/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2012 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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;
ConcurrentMap<FactoryManagerCacheKey, FactoryFinderInstance> applicationMap = new ConcurrentHashMap<>();
private AtomicBoolean logNullFacesContext = new AtomicBoolean();
private AtomicBoolean logNonNullFacesContext = new AtomicBoolean();
CurrentThreadToServletContext() {
servletContextFacesContextFactory = new ServletContextFacesContextFactory();
}
// ------------------------------------------------------ Public Methods
Object getFallbackFactory(FactoryFinderInstance brokenFactoryManager, String factoryName) {
ClassLoader classLoader = getContextClassLoader2();
for (Map.Entry<FactoryManagerCacheKey, FactoryFinderInstance> cur : applicationMap.entrySet()) {
if (cur.getKey().getClassLoader().equals(classLoader) && !cur.getValue().equals(brokenFactoryManager)) {
Object factory = cur.getValue().getFactory(factoryName);
if (factory != null) {
return factory;
}
}
}
return null;
}
FactoryFinderInstance getApplicationFactoryManager() {
return getApplicationFactoryManager(getContextClassLoader2(), true);
}
FactoryFinderInstance getApplicationFactoryManager(boolean create) {
return getApplicationFactoryManager(getContextClassLoader2(), create);
}
private FactoryFinderInstance getApplicationFactoryManager(ClassLoader classLoader, boolean create) {
FacesContext facesContext = servletContextFacesContextFactory.getFacesContextWithoutServletContextLookup();
boolean isSpecialInitializationCase = detectSpecialInitializationCase(facesContext);
FactoryManagerCacheKey key = new FactoryManagerCacheKey(facesContext, classLoader, applicationMap);
FactoryFinderInstance applicationFactoryManager = applicationMap.get(key);
FactoryFinderInstance toCopy = null;
if (applicationFactoryManager == null && create) {
boolean createNewFactoryManagerInstance = 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<FactoryManagerCacheKey, FactoryFinderInstance> cur : applicationMap.entrySet()) {
FactoryManagerCacheKey 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.
applicationFactoryManager = 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
createNewFactoryManagerInstance = foundNoMatchInApplicationMap || (applicationFactoryManager == null && classLoadersMatchButContextsDoNotMatch);
} else {
createNewFactoryManagerInstance = true;
}
if (createNewFactoryManagerInstance) {
FactoryFinderInstance newResult;
if (toCopy != null) {
newResult = new FactoryFinderInstance(toCopy);
} else {
newResult = new FactoryFinderInstance();
}
applicationFactoryManager = coalesce(applicationMap.putIfAbsent(key, newResult), newResult);
}
}
return applicationFactoryManager;
}
/**
* Uses the FactoryManagerCacheKey system to find the ServletContext associated with the current
* ClassLoader, if any.
*/
Object getServletContextForCurrentClassLoader() {
return new FactoryManagerCacheKey(null, getContextClassLoader2(), applicationMap).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 removeApplicationFactoryManager() {
ClassLoader classLoader = getContextClassLoader2();
FactoryFinderInstance applicationFactoryManager = getApplicationFactoryManager(classLoader, false);
if (applicationFactoryManager != null) {
applicationFactoryManager.clearInjectionProvider();
}
FacesContext facesContext = servletContextFacesContextFactory.getFacesContextWithoutServletContextLookup();
boolean isSpecialInitializationCase = detectSpecialInitializationCase(facesContext);
applicationMap.remove(new FactoryManagerCacheKey(facesContext, classLoader, applicationMap));
if (isSpecialInitializationCase) {
resetSpecialInitializationCaseFlags();
}
}
void resetSpecialInitializationCaseFlags() {
logNullFacesContext.set(false);
logNonNullFacesContext.set(false);
}
private static final class FactoryManagerCacheKey {
/**
* 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() + "." + FactoryManagerCacheKey.class.getSimpleName();
private static final String INIT_TIME_CL_KEY = MARKER_KEY + ".InitTimeCLKey";
FactoryManagerCacheKey(FacesContext facesContext, ClassLoader classLoaderIn, Map<FactoryManagerCacheKey, FactoryFinderInstance> factoryMap) {
ExternalContext extContext = facesContext != null ? facesContext.getExternalContext() : null;
Object servletContext = extContext != null ? extContext.getContext() : null;
if (isAnyNull(facesContext, extContext, servletContext)) {
initFromFactoryMap(classLoaderIn, factoryMap);
} else {
initFromAppMap(extContext, classLoaderIn);
}
}
private void initFromFactoryMap(ClassLoader classLoaderIn, Map<FactoryManagerCacheKey, FactoryFinderInstance> factoryMap) {
// We don't have a FacesContext.
//
// Our only recourse is to inspect the keys of the factoryMap and see if any of them has a classloader
// equal to our argument classLoader
Set<FactoryManagerCacheKey> keys = factoryMap.keySet();
FactoryManagerCacheKey matchingKey = null;
if (keys.isEmpty()) {
classLoader = classLoaderIn;
marker = currentTimeMillis();
} else {
// For each entry in the factoryMap's keySet...
for (FactoryManagerCacheKey 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 JSF Applications found on same ClassLoader. Unable to safely determine which FactoryManager 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 initFromAppMap(ExternalContext extContext, ClassLoader classLoaderIn) {
Map<String, Object> appMap = extContext.getApplicationMap();
Long val = (Long) appMap.get(MARKER_KEY);
if (val == null) {
marker = currentTimeMillis();
appMap.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.
appMap.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<String, Object> 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 FactoryManagerCacheKey other = (FactoryManagerCacheKey) 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);
}
}
}