javax.faces.CurrentThreadToServletContext Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jboss-jsf-api_2.3_spec Show documentation
Show all versions of jboss-jsf-api_2.3_spec Show documentation
JSR-000372: JavaServer(TM) Faces 2.3 API
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package javax.faces;
import static com.sun.faces.util.Util.coalesce;
import static com.sun.faces.util.Util.getContextClassLoader2;
import static com.sun.faces.util.Util.isAnyNull;
import static java.lang.System.currentTimeMillis;
import static java.lang.System.identityHashCode;
import static java.util.logging.Level.WARNING;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
final class CurrentThreadToServletContext {
private static final Logger LOGGER = Logger.getLogger("javax.faces", "javax.faces.LogStrings");
// Bug 20458755: This instance provides a method to look up the current FacesContext
// that bypasses the additional check for the InitFacesContext introduced
// by the fix for 20458755
private final ServletContextFacesContextFactory servletContextFacesContextFactory = new ServletContextFacesContextFactory();
ConcurrentMap factoryFinderMap = new ConcurrentHashMap<>();
private AtomicBoolean logNullFacesContext = new AtomicBoolean();
private AtomicBoolean logNonNullFacesContext = new AtomicBoolean();
// ------------------------------------------------------ Public Methods
FactoryFinderInstance getFactoryFinder() {
return getFactoryFinder(getContextClassLoader2(), true);
}
FactoryFinderInstance getFactoryFinder(boolean create) {
return getFactoryFinder(getContextClassLoader2(), create);
}
private FactoryFinderInstance getFactoryFinder(ClassLoader classLoader, boolean create) {
FacesContext facesContext = servletContextFacesContextFactory.getFacesContextWithoutServletContextLookup();
boolean isSpecialInitializationCase = detectSpecialInitializationCase(facesContext);
FactoryFinderCacheKey key = new FactoryFinderCacheKey(facesContext, classLoader, factoryFinderMap);
FactoryFinderInstance factoryFinder = factoryFinderMap.get(key);
FactoryFinderInstance toCopy = null;
if (factoryFinder == null && create) {
boolean createNewFactoryFinderInstance = false;
if (isSpecialInitializationCase) {
// We need to obtain a reference to the correct FactoryFinderInstance.
// Iterate through the data structure containing all FactoryFinderInstance instances for this VM.
boolean classLoadersMatchButContextsDoNotMatch = false;
boolean foundNoMatchInApplicationMap = true;
for (Map.Entry cur : factoryFinderMap.entrySet()) {
FactoryFinderCacheKey curKey = cur.getKey();
// If the current FactoryFinderInstance is for the same ClassLoader as the current ClassLoader...
if (curKey.getClassLoader().equals(classLoader)) {
foundNoMatchInApplicationMap = false;
// Check the other discriminator for the key: the context.
// If the context objects of the keys are both non-null and non-equal, then *do*
// create a new FactoryFinderInstance instance.
if (!isAnyNull(key.getContext(), curKey.getContext()) && !key.getContext().equals(curKey.getContext())) {
classLoadersMatchButContextsDoNotMatch = true;
toCopy = cur.getValue();
} else {
// Otherwise, use this FactoryFinderInstance instance.
factoryFinder = cur.getValue();
}
break;
}
}
// We must create a new FactoryFinderInstance if there was no matchingKey
// at all found in the applicationMap, or a matchingKey was found
// and the matchingKey is safe to use in this web app
createNewFactoryFinderInstance = foundNoMatchInApplicationMap || (factoryFinder == null && classLoadersMatchButContextsDoNotMatch);
} else {
createNewFactoryFinderInstance = true;
}
if (createNewFactoryFinderInstance) {
FactoryFinderInstance newResult;
if (toCopy != null) {
newResult = new FactoryFinderInstance(facesContext, toCopy);
} else {
newResult = new FactoryFinderInstance(facesContext);
}
factoryFinder = coalesce(factoryFinderMap.putIfAbsent(key, newResult), newResult);
}
}
return factoryFinder;
}
Object getFallbackFactory(FactoryFinderInstance brokenFactoryManager, String factoryName) {
ClassLoader classLoader = getContextClassLoader2();
for (Map.Entry cur : factoryFinderMap.entrySet()) {
if (cur.getKey().getClassLoader().equals(classLoader) && !cur.getValue().equals(brokenFactoryManager)) {
Object factory = cur.getValue().getFactory(factoryName);
if (factory != null) {
return factory;
}
}
}
return null;
}
/**
* Uses the FactoryManagerCacheKey system to find the ServletContext associated with the current
* ClassLoader, if any.
*/
Object getServletContextForCurrentClassLoader() {
return new FactoryFinderCacheKey(null, getContextClassLoader2(), factoryFinderMap).getContext();
}
/**
* This method is used to detect the following special initialization case. IF no
* FactoryFinderInstance can be found for key, AND this call to
* getApplicationFactoryFinderInstance() *does* have a currentKeyrent FacesContext BUT a
* previous call to getApplicationFactoryFinderInstance *did not* have a currentKeyrent
* FacesContext
*
* @param facesContext the currentKeyrent FacesContext for this request
* @return true if the currentKeyrent execution falls into the special initialization case.
*/
private boolean detectSpecialInitializationCase(FacesContext facesContext) {
if (facesContext == null) {
logNullFacesContext.compareAndSet(false, true);
} else {
logNonNullFacesContext.compareAndSet(false, true);
}
return logNullFacesContext.get() && logNonNullFacesContext.get();
}
void removeFactoryFinder() {
ClassLoader classLoader = getContextClassLoader2();
FactoryFinderInstance factoryFinder = getFactoryFinder(classLoader, false);
if (factoryFinder != null) {
factoryFinder.clearInjectionProvider();
}
FacesContext facesContext = servletContextFacesContextFactory.getFacesContextWithoutServletContextLookup();
boolean isSpecialInitializationCase = detectSpecialInitializationCase(facesContext);
factoryFinderMap.remove(new FactoryFinderCacheKey(facesContext, classLoader, factoryFinderMap));
if (isSpecialInitializationCase) {
resetSpecialInitializationCaseFlags();
}
}
void resetSpecialInitializationCaseFlags() {
logNullFacesContext.set(false);
logNonNullFacesContext.set(false);
}
private static final class FactoryFinderCacheKey {
/**
* The ClassLoader that is active the first time this key is created.
* At startup time, this is assumed to be the web app ClassLoader
*/
private ClassLoader classLoader;
/**
* A marker that disambiguates the case when multiple web apps have the same web app
* ClassLoader but different ServletContext instances.
*/
private Long marker;
/**
* The ServletContext corresponding to this marker/ClassLoader pair.
*/
private Object context;
private static final String MARKER_KEY = FactoryFinder.class.getName() + "." + FactoryFinderCacheKey.class.getSimpleName();
private static final String INIT_TIME_CL_KEY = MARKER_KEY + ".InitTimeCLKey";
FactoryFinderCacheKey(FacesContext facesContext, ClassLoader classLoaderIn, Map factoryMap) {
ExternalContext extContext = facesContext != null ? facesContext.getExternalContext() : null;
Object servletContext = extContext != null ? extContext.getContext() : null;
if (isAnyNull(facesContext, extContext, servletContext)) {
initFromFactoryMap(classLoaderIn, factoryMap);
} else {
initFromApplicationMap(extContext, classLoaderIn);
}
}
private void initFromFactoryMap(ClassLoader classLoaderIn, Map factoryFinderMap) {
// We don't have a FacesContext.
//
// Our only recourse is to inspect the keys of the factoryFinderMap and see if any of them has a classloader
// equal to our argument classLoader
Set keys = factoryFinderMap.keySet();
FactoryFinderCacheKey matchingKey = null;
if (keys.isEmpty()) {
classLoader = classLoaderIn;
marker = currentTimeMillis();
} else {
// For each entry in the factoryMap's keySet...
for (FactoryFinderCacheKey currentKey : keys) {
ClassLoader matchingClassLoader = findMatchConsideringParentClassLoader(classLoaderIn, currentKey.classLoader);
// If there is a match...
if (matchingClassLoader != null) {
// If the match was found on a previous iteration...
if (matchingKey != null) {
// LOGGER.log(WARNING,
// "Multiple Jakarta Server Faces Applications found on same ClassLoader. Unable to safely determine which FactoryFinder instance to use. Defaulting to first match.");
break;
}
matchingKey = currentKey;
classLoader = matchingClassLoader;
}
}
if (matchingKey != null) {
marker = matchingKey.marker;
context = matchingKey.context;
}
}
}
private ClassLoader findMatchConsideringParentClassLoader(ClassLoader argumentClassLoader, ClassLoader currentKeyCL) {
ClassLoader currentClassLoader = argumentClassLoader;
// For each ClassLoader in the hierarchy starting with the argument ClassLoader...
while (currentClassLoader != null) {
// If the ClassLoader at this level in the hierarchy is equal to the argument ClassLoader,
// consider it a matchingKey.
if (currentClassLoader.equals(currentKeyCL)) {
return currentClassLoader;
} else {
// If it's not a matchingKey, try the parent in the ClassLoader hierarchy.
currentClassLoader = currentClassLoader.getParent();
}
}
return null;
}
private void initFromApplicationMap(ExternalContext extContext, ClassLoader classLoaderIn) {
Map applicationMap = extContext.getApplicationMap();
Long val = (Long) applicationMap.get(MARKER_KEY);
if (val == null) {
marker = currentTimeMillis();
applicationMap.put(MARKER_KEY, marker);
// If we needed to create a marker, assume that the argument CL is safe to treat as the web app
// ClassLoader.
//
// This assumption allows us to bypass the ClassLoader resolution algorithm in resolveToFirstTimeUsedClassLoader()
// in all cases except when the TCCL has been replaced.
applicationMap.put(INIT_TIME_CL_KEY, identityHashCode(classLoaderIn));
} else {
marker = val;
}
classLoader = resolveToFirstTimeUsedClassLoader(classLoaderIn, extContext);
context = extContext.getContext();
}
/**
* Resolve the argument ClassLoader to be the ClassLoader that was passed in to the ctor the
* first time a FactoryManagerCacheKey was created for this web app.
*/
private ClassLoader resolveToFirstTimeUsedClassLoader(ClassLoader classLoaderToResolve, ExternalContext extContext) {
ClassLoader currentClassLoader = classLoaderToResolve;
Map appMap = extContext.getApplicationMap();
// See if the argument currentClassLoader already is the web app class loader
Integer webAppCLHashCode = (Integer) appMap.get(INIT_TIME_CL_KEY);
boolean found = false;
if (webAppCLHashCode != null) {
int toResolveHashCode = identityHashCode(currentClassLoader);
while (!found && currentClassLoader != null) {
found = toResolveHashCode == webAppCLHashCode;
if (!found) {
currentClassLoader = currentClassLoader.getParent();
toResolveHashCode = identityHashCode(currentClassLoader);
}
}
}
return found ? currentClassLoader : classLoaderToResolve;
}
ClassLoader getClassLoader() {
return classLoader;
}
Object getContext() {
return context;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final FactoryFinderCacheKey other = (FactoryFinderCacheKey) obj;
if (this.classLoader != other.classLoader && (this.classLoader == null || !this.classLoader.equals(other.classLoader))) {
return false;
}
if (this.marker != other.marker && (this.marker == null || !this.marker.equals(other.marker))) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + (classLoader != null ? this.classLoader.hashCode() : 0);
return 97 * hash + (marker != null ? marker.hashCode() : 0);
}
}
}