/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 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.dev.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 com.sun.
faces.
spi.
InjectionProvider;
import java.io.
BufferedReader;
import java.io.
InputStream;
import java.io.
InputStreamReader;
import java.io.
IOException;
import java.io.
UnsupportedEncodingException;
import java.text.
MessageFormat;
import java.util.
ArrayList;
import java.util.
HashMap;
import java.util.
List;
import java.util.
Map;
import java.util.
ResourceBundle;
import java.util.
Arrays;
import java.util.
Enumeration;
import java.util.concurrent.
ConcurrentMap;
import java.util.concurrent.
ConcurrentHashMap;
import java.util.concurrent.locks.
ReentrantReadWriteLock;
import java.util.logging.
Logger;
import java.util.logging.
Level;
import java.lang.reflect.
Constructor;
import java.net.
URL;
import java.net.
URLConnection;
import java.util.
Collection;
import java.util.
Set;
import java.util.concurrent.atomic.
AtomicBoolean;
import javax.faces.context.
ExternalContext;
import javax.faces.context.
FacesContext;
/**
* <p><strong class="changed_modified_2_0 changed_modified_2_1 changed_modified_2_2">FactoryFinder</strong>
* implements the standard discovery algorithm for all factory objects
* specified in the JavaServer Faces APIs. For a given factory class
* name, a corresponding implementation class is searched for based on
* the following algorithm. Items are listed in order of decreasing
* search precedence:</p>
* <ul>
* <li><p>If the JavaServer Faces configuration file bundled into the
* <code>WEB-INF</code> directory of the webapp contains a
* <code>factory</code> entry of the given factory class name, that
* factory is used.<p></li>
* <li><p>If the JavaServer Faces configuration files named by the
* <code>javax.faces.CONFIG_FILES</code> <code>ServletContext</code> init
* parameter contain any <code>factory</code> entries of the given
* factory class name, those injectionProvider are used, with the last one taking
* precedence.</p></li>
* <li><p>If there are any JavaServer Faces configuration files bundled
* into the <code>META-INF</code> directory of any jars on the
* <code>ServletContext</code>'s resource paths, the
* <code>factory</code> entries of the given factory class name in those
* files are used, with the last one taking precedence.</p></li>
* <li><p>If a <code>META-INF/services/{factory-class-name}</code>
* resource is visible to the web application class loader for the
* calling application (typically as a injectionProvider of being present in the
* manifest of a JAR file), its first line is read and assumed to be the
* name of the factory implementation class to use.</p></li>
* <li><p>If none of the above steps yield a match, the JavaServer Faces
* implementation specific class is used.</p></li>
* </ul>
* <p>If any of the injectionProvider found on any of the steps above happen to
* have a one-argument constructor, with argument the type being the
* abstract factory class, that constructor is invoked, and the previous
* match is passed to the constructor. For example, say the container
* vendor provided an implementation of {@link
* javax.faces.context.FacesContextFactory}, and identified it in
* <code>META-INF/services/javax.faces.context.FacesContextFactory</code>
* in a jar on the webapp ClassLoader. Also say this implementation
* provided by the container vendor had a one argument constructor that
* took a <code>FacesContextFactory</code> instance. The
* <code>FactoryFinder</code> system would call that one-argument
* constructor, passing the implementation of
* <code>FacesContextFactory</code> provided by the JavaServer Faces
* implementation.</p>
* <p>If a Factory implementation does not provide a proper one-argument
* constructor, it must provide a zero-arguments constructor in order to
* be successfully instantiated.</p>
* <p>Once the name of the factory implementation class is located, the
* web application class loader for the calling application is requested
* to load this class, and a corresponding instance of the class will be
* created. A side effect of this rule is that each web application
* will receive its own instance of each factory class, whether the
* JavaServer Faces implementation is included within the web
* application or is made visible through the container's facilities for
* shared libraries.</p>
*/
public final class
FactoryFinder {
// ----------------------------------------------------------- Constructors
/**
* Package-private constructor to disable instantiation of this class.
*/
FactoryFinder() {
}
// ----------------------------------------------------- Manifest Constants
/**
* <p>The property name for the
* {@link javax.faces.application.ApplicationFactory} class name.</p>
*/
public final static
String APPLICATION_FACTORY =
"javax.faces.application.ApplicationFactory";
/**
* <p>The property name for the
* {@link javax.faces.lifecycle.ClientWindowFactory} class name.</p>
* @since 2.2
*/
public final static
String CLIENT_WINDOW_FACTORY =
"javax.faces.lifecycle.ClientWindowFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.context.ExceptionHandlerFactory} class name.</p>
*/
public final static
String EXCEPTION_HANDLER_FACTORY =
"javax.faces.context.ExceptionHandlerFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.context.ExternalContextFactory} class name.</p>
*/
public final static
String EXTERNAL_CONTEXT_FACTORY =
"javax.faces.context.ExternalContextFactory";
/**
* <p>The property name for the
* {@link javax.faces.context.FacesContextFactory} class name.</p>
*/
public final static
String FACES_CONTEXT_FACTORY =
"javax.faces.context.FacesContextFactory";
/**
* <p class="changed_added_2_1">The property name for the
* {@link javax.faces.view.facelets.FaceletCacheFactory} class name.</p>
*
* @since 2.1
*/
public final static
String FACELET_CACHE_FACTORY =
"javax.faces.view.facelets.FaceletCacheFactory";
/**
* <p class="changed_added_2_2">The property name for the
* {@link javax.faces.context.FlashFactory} class name.</p>
*
* @since 2.2
*/
public final static
String FLASH_FACTORY =
"javax.faces.context.FlashFactory";
/**
* <p class="changed_added_2_2">The property name for the
* {@link javax.faces.flow.FlowHandlerFactory} class name.</p>
*
* @since 2.2
*/
public final static
String FLOW_HANDLER_FACTORY =
"javax.faces.flow.FlowHandlerFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.context.PartialViewContextFactory} class name.</p>
*/
public final static
String PARTIAL_VIEW_CONTEXT_FACTORY =
"javax.faces.context.PartialViewContextFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.component.visit.VisitContextFactory} class name.</p>
*/
public final static
String VISIT_CONTEXT_FACTORY =
"javax.faces.component.visit.VisitContextFactory";
/**
* <p>The property name for the
* {@link javax.faces.lifecycle.LifecycleFactory} class name.</p>
*/
public final static
String LIFECYCLE_FACTORY =
"javax.faces.lifecycle.LifecycleFactory";
/**
* <p>The property name for the
* {@link javax.faces.render.RenderKitFactory} class name.</p>
*/
public final static
String RENDER_KIT_FACTORY =
"javax.faces.render.RenderKitFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.view.ViewDeclarationLanguage} class name.</p>
*/
public final static
String VIEW_DECLARATION_LANGUAGE_FACTORY =
"javax.faces.view.ViewDeclarationLanguageFactory";
/**
* <p class="changed_added_2_0">The property name for the {@link
* javax.faces.view.facelets.TagHandlerDelegate} class name.</p>
*/
public final static
String TAG_HANDLER_DELEGATE_FACTORY =
"javax.faces.view.facelets.TagHandlerDelegateFactory";
// ------------------------------------------------------- Static Variables
private static final
FactoryManagerCache FACTORIES_CACHE;
/**
* <p>The set of JavaServer Faces factory classes for which the factory
* discovery mechanism is supported. The entries in this list must be
* alphabetically ordered according to the entire string of the
* *value* of each of the literals, not just
* the last part of the literal!</p>
*/
private static final
String[]
FACTORY_NAMES;
/**
* <p>Map of Class instances for the our factory names.</p>
*/
private static
Map<
String,
Class>
factoryClasses;
private static final
Logger LOGGER;
static {
FACTORIES_CACHE = new
FactoryManagerCache();
FACTORY_NAMES = new
String [] {
APPLICATION_FACTORY,
VISIT_CONTEXT_FACTORY,
EXCEPTION_HANDLER_FACTORY,
EXTERNAL_CONTEXT_FACTORY,
FACES_CONTEXT_FACTORY,
FLASH_FACTORY,
FLOW_HANDLER_FACTORY,
PARTIAL_VIEW_CONTEXT_FACTORY,
CLIENT_WINDOW_FACTORY,
LIFECYCLE_FACTORY,
RENDER_KIT_FACTORY,
VIEW_DECLARATION_LANGUAGE_FACTORY,
FACELET_CACHE_FACTORY,
TAG_HANDLER_DELEGATE_FACTORY
};
// Ensure the factory names are sorted.
//
Arrays.
sort(
FACTORY_NAMES);
factoryClasses = new
HashMap<
String,
Class>(
FACTORY_NAMES.length);
factoryClasses.
put(
APPLICATION_FACTORY,
javax.faces.application.
ApplicationFactory.class);
factoryClasses.
put(
VISIT_CONTEXT_FACTORY,
javax.faces.component.visit.
VisitContextFactory.class);
factoryClasses.
put(
EXCEPTION_HANDLER_FACTORY,
javax.faces.context.
ExceptionHandlerFactory.class);
factoryClasses.
put(
EXTERNAL_CONTEXT_FACTORY,
javax.faces.context.
ExternalContextFactory.class);
factoryClasses.
put(
FACES_CONTEXT_FACTORY,
javax.faces.context.
FacesContextFactory.class);
factoryClasses.
put(
FLASH_FACTORY,
javax.faces.context.
FlashFactory.class);
factoryClasses.
put(
PARTIAL_VIEW_CONTEXT_FACTORY,
javax.faces.context.
PartialViewContextFactory.class);
factoryClasses.
put(
LIFECYCLE_FACTORY,
javax.faces.lifecycle.
LifecycleFactory.class);
factoryClasses.
put(
CLIENT_WINDOW_FACTORY,
javax.faces.lifecycle.
ClientWindowFactory.class);
factoryClasses.
put(
RENDER_KIT_FACTORY,
javax.faces.render.
RenderKitFactory.class);
factoryClasses.
put(
VIEW_DECLARATION_LANGUAGE_FACTORY,
javax.faces.view.
ViewDeclarationLanguageFactory.class);
factoryClasses.
put(
FACELET_CACHE_FACTORY,
javax.faces.view.facelets.
FaceletCacheFactory.class);
factoryClasses.
put(
TAG_HANDLER_DELEGATE_FACTORY,
javax.faces.view.facelets.
TagHandlerDelegateFactory.class);
factoryClasses.
put(
FLOW_HANDLER_FACTORY,
javax.faces.flow.
FlowHandlerFactory.class);
LOGGER =
Logger.
getLogger("javax.faces", "javax.faces.LogStrings");
}
// --------------------------------------------------------- Public Methods
/**
* <p><span class="changed_modified_2_0">Create</span> (if
* necessary) and return a per-web-application instance of the
* appropriate implementation class for the specified JavaServer
* Faces factory class, based on the discovery algorithm described
* in the class description.</p>
*
* <p class="changed_added_2_0">The standard injectionProvider and wrappers
* in JSF all implement the interface {@link FacesWrapper}. If the
* returned <code>Object</code> is an implementation of one of the
* standard injectionProvider, it must be legal to cast it to an instance of
* <code>FacesWrapper</code> and call {@link
* FacesWrapper#getWrapped} on the instance.</p>
*
* @param factoryName Fully qualified name of the JavaServer Faces factory
* for which an implementation instance is requested
* @throws FacesException if the web application class loader
* cannot be identified
* @throws FacesException if an instance of the configured factory
* implementation class cannot be loaded
* @throws FacesException if an instance of the configured factory
* implementation class cannot be instantiated
* @throws IllegalArgumentException if <code>factoryName</code> does not
* identify a standard JavaServer Faces factory name
* @throws IllegalStateException if there is no configured factory
* implementation class for the specified factory name
* @throws NullPointerException if <code>factoryname</code>
* is null
*/
public static
Object getFactory(
String factoryName)
throws
FacesException {
validateFactoryName(
factoryName);
// Identify the web application class loader
ClassLoader classLoader =
getClassLoader();
FactoryManager manager =
FACTORIES_CACHE.
getApplicationFactoryManager(
classLoader);
return
manager.
getFactory(
classLoader,
factoryName);
}
/**
* <p>This method will store the argument
* <code>factoryName/implName</code> mapping in such a way that
* {@link #getFactory} will find this mapping when searching for a
* match.</p>
* <p/>
* <p>This method has no effect if <code>getFactory()</code> has
* already been called looking for a factory for this
* <code>factoryName</code>.</p>
* <p/>
* <p>This method can be used by implementations to store a factory
* mapping while parsing the Faces configuration file</p>
*
* @throws IllegalArgumentException if <code>factoryName</code> does not
* identify a standard JavaServer Faces factory name
* @throws NullPointerException if <code>factoryname</code>
* is null
*/
public static void
setFactory(
String factoryName,
String implName) {
validateFactoryName(
factoryName);
// Identify the web application class loader
ClassLoader classLoader =
getClassLoader();
FactoryManager manager =
FACTORIES_CACHE.
getApplicationFactoryManager(
classLoader);
manager.
addFactory(
factoryName,
implName);
}
/**
* <p><span class="changed_modified_2_0">Release</span> any
* references to factory instances associated with the class loader
* for the calling web application. <span
* class="changed_modified_2_0">This method must be called during of
* web application shutdown.</span></p>
*
* @throws FacesException if the web application class loader
* cannot be identified
*/
public static void
releaseFactories() throws
FacesException {
// Identify the web application class loader
ClassLoader cl =
getClassLoader();
if (!
FACTORIES_CACHE.
applicationMap.
isEmpty()) {
FactoryManager fm =
FACTORIES_CACHE.
getApplicationFactoryManager(
cl);
InjectionProvider provider =
fm.
getInjectionProvider();
if (null !=
provider) {
Collection factories = null;
for (
Map.
Entry<
FactoryManagerCacheKey,
FactoryManager>
entry :
FACTORIES_CACHE.
applicationMap.
entrySet()) {
factories =
entry.
getValue().
getFactories();
for (
Object curFactory :
factories) {
try {
provider.
invokePreDestroy(
curFactory);
} catch (
Exception ex) {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
String message =
MessageFormat.
format("Unable to invoke @PreDestroy annotated methods on {0}.",
curFactory);
LOGGER.
log(
Level.
SEVERE,
message,
ex);
}
}
}
}
} else {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE, "Unable to call @PreDestroy annotated methods because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?");
}
}
}
FACTORIES_CACHE.
removeApplicationFactoryManager(
cl);
}
// -------------------------------------------------------- Private Methods
/**
* <p>Identify and return the class loader that is associated with the
* calling web application.</p>
*
* @throws FacesException if the web application class loader
* cannot be identified
*/
private static
ClassLoader getClassLoader() throws
FacesException {
// J2EE 1.3 (and later) containers are required to make the
// web application class loader visible through the context
// class loader of the current thread.
ClassLoader cl =
Thread.
currentThread().
getContextClassLoader();
if (
cl == null) {
throw new
FacesException("getContextClassLoader");
}
return (
cl);
}
/**
* <p>Load and return an instance of the specified implementation
* class using the following algorithm.</p>
* <p/>
* <ol>
* <p/>
* <li><p>If the argument <code>implementations</code> list has
* more than one element, or exactly one element, interpret the
* last element in the list to be the fully qualified class name of
* a class implementing <code>factoryName</code>. Instantiate that
* class and save it for return. If the
* <code>implementations</code> list has only one element, skip
* this step.</p></li>
* <p/>
* <li><p>Look for a resource called
* <code>/META-INF/services/<factoryName></code>. If found,
* interpret it as a properties file, and read out the first entry.
* Interpret the first entry as a fully qualify class name of a
* class that implements <code>factoryName</code>. If we have an
* instantiated factory from the previous step <em>and</em> the
* implementing class has a one arg constructor of the type for
* <code>factoryName</code>, instantiate it, passing the
* instantiated factory from the previous step. If there is no one
* arg constructor, just instantiate the zero arg constructor. Save
* the newly instantiated factory for return, replacing the
* instantiated factory from the previous step.</p></li>
* <p/>
* <li><p>Treat each remaining element in the
* <code>implementations</code> list as a fully qualified class name
* of a class implementing <code>factoryName</code>. If the current
* element has a one arg constructor of the type for
* <code>factoryName</code>, instantiate it, passing the
* instantiated factory from the previous or step iteration. If
* there is no one arg constructor, just instantiate the zero arg
* constructor, replacing the instantiated factory from the previous
* step or iteration.</p></li>
* <p/>
* <li><p>Return the saved factory</p></li>
* <p/>
* </ol>
*
* @param classLoader Class loader for the web application that will
* be loading the implementation class
* @param implementations A List of implementations for a given
* factory class.
* @throws FacesException if the specified implementation class
* cannot be loaded
* @throws FacesException if an instance of the specified implementation
* class cannot be instantiated
*/
private static
Object getImplementationInstance(
ClassLoader classLoader,
String factoryName,
List implementations)
throws
FacesException {
Object result = null;
String curImplClass;
int
len;
// step 1.
if (null !=
implementations &&
(1 < (
len =
implementations.
size()) || 1 ==
len)) {
curImplClass = (
String)
implementations.
remove(
len - 1);
// since this is the hard coded implementation default,
// there is no preceding implementation, so don't bother
// with a non-zero-arg ctor.
result =
getImplGivenPreviousImpl(
classLoader,
factoryName,
curImplClass, null);
}
// step 2.
List<
String>
fromServices =
getImplNameFromServices(
classLoader,
factoryName);
if (
fromServices != null) {
for (
String name :
fromServices) {
result =
getImplGivenPreviousImpl(
classLoader,
factoryName,
name,
result);
}
}
// step 3.
if (null !=
implementations) {
for (
len = (
implementations.
size() - 1); 0 <=
len;
len--) {
curImplClass = (
String)
implementations.
remove(
len);
result =
getImplGivenPreviousImpl(
classLoader,
factoryName,
curImplClass,
result);
}
}
return
result;
}
/**
* <p>Perform the logic to get the implementation class for the
* second step of {@link FactoryFinder#getImplementationInstance(ClassLoader, String, java.util.List)}.</p>
*/
private static
List<
String>
getImplNameFromServices(
ClassLoader classLoader,
String factoryName) {
// Check for a services definition
List<
String>
result = null;
String resourceName = "META-INF/services/" +
factoryName;
InputStream stream;
BufferedReader reader = null;
try {
Enumeration<
URL>
e =
classLoader.
getResources(
resourceName);
while (
e.
hasMoreElements()) {
URL url =
e.
nextElement();
URLConnection conn =
url.
openConnection();
conn.
setUseCaches(false);
stream =
conn.
getInputStream();
if (
stream != null) {
// Deal with systems whose native encoding is possibly
// different from the way that the services entry was created
try {
reader =
new
BufferedReader(new
InputStreamReader(
stream,
"UTF-8"));
if (
result == null) {
result = new
ArrayList<
String>(3);
}
result.
add(
reader.
readLine());
} catch (
UnsupportedEncodingException uee) {
// The DM_DEFAULT_ENCODING warning is acceptable here
// because we explicitly *want* to use the Java runtime's
// default encoding.
reader =
new
BufferedReader(new
InputStreamReader(
stream));
} finally {
if (
reader != null) {
reader.
close();
reader = null;
}
if (
stream != null) {
stream.
close();
//noinspection UnusedAssignment
stream = null;
}
}
}
}
} catch (
IOException e) {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE,
e.
toString(),
e);
}
} catch (
SecurityException e) {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE,
e.
toString(),
e);
}
}
return
result;
}
/**
* <p>Implement the decorator pattern for the factory
* implementation.</p>
* <p/>
* <p>If <code>previousImpl</code> is non-<code>null</code> and the
* class named by the argument <code>implName</code> has a one arg
* contstructor of type <code>factoryName</code>, instantiate it,
* passing previousImpl to the constructor.</p>
* <p/>
* <p>Otherwise, we just instantiate and return
* <code>implName</code>.</p>
*
* @param classLoader the ClassLoader from which to load the class
* @param factoryName the fully qualified class name of the factory.
* @param implName the fully qualified class name of a class that
* implements the factory.
* @param previousImpl if non-<code>null</code>, the factory
* instance to be passed to the constructor of the new factory.
*/
private static
Object getImplGivenPreviousImpl(
ClassLoader classLoader,
String factoryName,
String implName,
Object previousImpl) {
Class clazz;
Class factoryClass = null;
Class[]
getCtorArg;
Object[]
newInstanceArgs = new
Object[1];
Constructor ctor;
Object result = null;
InjectionProvider provider = null;
// if we have a previousImpl and the appropriate one arg ctor.
if ((null !=
previousImpl) &&
(null != (
factoryClass =
getFactoryClass(
factoryName)))) {
try {
clazz =
Class.
forName(
implName, false,
classLoader);
getCtorArg = new
Class[1];
getCtorArg[0] =
factoryClass;
ctor =
clazz.
getConstructor(
getCtorArg);
newInstanceArgs[0] =
previousImpl;
result =
ctor.
newInstance(
newInstanceArgs);
FactoryManager fm =
FACTORIES_CACHE.
getApplicationFactoryManager(
classLoader);
provider =
fm.
getInjectionProvider();
if (null !=
provider) {
provider.
inject(
result);
provider.
invokePostConstruct(
result);
} else {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE, "Unable to inject {0} because no InjectionProvider can be found. Does this container implement the Mojarra Injection SPI?",
result);
}
}
}
catch (
NoSuchMethodException nsme) {
// fall through to "zero-arg-ctor" case
factoryClass = null;
}
catch (
Exception e) {
throw new
FacesException(
implName,
e);
}
}
if (null ==
previousImpl || null ==
factoryClass) {
// we have either no previousImpl or no appropriate one arg
// ctor.
try {
clazz =
Class.
forName(
implName, false,
classLoader);
// since this is the hard coded implementation default,
// there is no preceding implementation, so don't bother
// with a non-zero-arg ctor.
result =
clazz.
newInstance();
} catch (
Exception e) {
throw new
FacesException(
implName,
e);
}
}
return
result;
}
/**
* @return the <code>java.lang.Class</code> for the argument
* factory.
*/
private static
Class getFactoryClass(
String factoryClassName) {
return
factoryClasses.
get(
factoryClassName);
}
/**
* Ensure the provided factory name is valid.
*/
private static void
validateFactoryName(
String factoryName) {
if (
factoryName == null) {
throw new
NullPointerException();
}
if (
Arrays.
binarySearch(
FACTORY_NAMES,
factoryName) < 0) {
throw new
IllegalArgumentException(
factoryName);
}
}
private static void
reInitializeFactoryManager() {
FACTORIES_CACHE.
resetSpecialInitializationCaseFlags();
}
// ----------------------------------------------------------- Inner Classes
/**
* Managed the mappings between a web application and its configured
* injectionProvider.
*/
private static final class
FactoryManagerCache {
private
ConcurrentMap<
FactoryManagerCacheKey,
FactoryManager>
applicationMap =
new
ConcurrentHashMap<
FactoryManagerCacheKey,
FactoryManager>();
private
AtomicBoolean logNullFacesContext = new
AtomicBoolean(false);
private
AtomicBoolean logNonNullFacesContext = new
AtomicBoolean(false);
// ------------------------------------------------------ Public Methods
private
Object getFallbackFactory(
ClassLoader cl,
FactoryManager brokenFactoryManager,
String factoryName) {
Object result = null;
for (
Map.
Entry<
FactoryManagerCacheKey,
FactoryManager>
cur :
applicationMap.
entrySet()) {
if (
cur.
getKey().
getClassLoader().
equals(
cl) && !
cur.
getValue().
equals(
brokenFactoryManager)) {
result =
cur.
getValue().
getFactory(
cl,
factoryName);
if (null !=
result) {
break;
}
}
}
return
result;
}
private
FactoryManager getApplicationFactoryManager(
ClassLoader cl) {
FactoryManager result =
getApplicationFactoryManager(
cl, true);
return
result;
}
private
FactoryManager getApplicationFactoryManager(
ClassLoader cl, boolean
create) {
FacesContext facesContext =
FacesContext.
getCurrentInstance();
boolean
isSpecialInitializationCase =
detectSpecialInitializationCase(
facesContext);
FactoryManagerCacheKey key = new
FactoryManagerCacheKey(
facesContext,
cl,
applicationMap);
FactoryManager result =
applicationMap.
get(
key);
FactoryManager toCopy = null;
if (
result == null &&
create) {
boolean
createNewFactoryManagerInstance = false;
if (
isSpecialInitializationCase) {
// We need to obtain a reference to the correct
// FactoryManager. Iterate through the data structure
// containing all FactoryManager instances for this VM.
FactoryManagerCacheKey curKey;
boolean
classLoadersMatchButContextsDoNotMatch = false;
boolean
foundNoMatchInApplicationMap = true;
for (
Map.
Entry<
FactoryManagerCacheKey,
FactoryManager>
cur :
applicationMap.
entrySet()) {
curKey =
cur.
getKey();
// If the current FactoryManager is for a
// the same ClassLoader as the current ClassLoader...
if (
curKey.
getClassLoader().
equals(
cl)) {
foundNoMatchInApplicationMap = false;
// Check the other descriminator for the
// key: the context.
// If the context objects of the keys are
// both non-null and non-equal, then *do*
// create a new FactoryManager instance.
if ((null !=
key.
getContext() && null !=
curKey.
getContext()) &&
(!
key.
getContext().
equals(
curKey.
getContext()))) {
classLoadersMatchButContextsDoNotMatch = true;
toCopy =
cur.
getValue();
}
else {
// Otherwise, use this FactoryManager
// instance.
result =
cur.
getValue();
}
break;
}
}
// We must create a new FactoryManager if there was no match
// at all found in the applicationMap, or a match was found
// and the match is safe to use in this web app
createNewFactoryManagerInstance =
foundNoMatchInApplicationMap ||
(null ==
result &&
classLoadersMatchButContextsDoNotMatch);
} else {
createNewFactoryManagerInstance = true;
}
if (
createNewFactoryManagerInstance) {
FactoryManager newResult;
if (null !=
toCopy) {
newResult = new
FactoryManager(
toCopy);
} else {
newResult = new
FactoryManager();
}
result =
applicationMap.
putIfAbsent(
key,
newResult);
result = (null !=
result) ?
result :
newResult;
}
}
return
result;
}
/**
* This method is used to detect the following special initialization case.
* IF no FactoryManager can be found for key,
* AND this call to getApplicationFactoryManager() *does* have a current FacesContext
* BUT a previous call to getApplicationFactoryManager *did not* have a current FacesContext
*
* @param facesContext the current FacesContext for this request
* @return true if the current execution falls into the special initialization case.
*/
private boolean
detectSpecialInitializationCase(
FacesContext facesContext) {
boolean
result = false;
if (null ==
facesContext) {
logNullFacesContext.
compareAndSet(false, true);
} else {
logNonNullFacesContext.
compareAndSet(false, true);
}
result =
logNullFacesContext.
get() &&
logNonNullFacesContext.
get();
return
result;
}
public void
removeApplicationFactoryManager(
ClassLoader cl) {
FactoryManager fm = this.
getApplicationFactoryManager(
cl, false);
if (null !=
fm) {
fm.
clearInjectionProvider();
}
FacesContext facesContext =
FacesContext.
getCurrentInstance();
boolean
isSpecialInitializationCase =
detectSpecialInitializationCase(
facesContext);
FactoryManagerCacheKey key = new
FactoryManagerCacheKey(
facesContext,
cl,
applicationMap);
applicationMap.
remove(
key);
if (
isSpecialInitializationCase) {
logNullFacesContext.
set(false);
logNonNullFacesContext.
set(false);
}
}
public void
resetSpecialInitializationCaseFlags() {
logNullFacesContext.
set(false);
logNonNullFacesContext.
set(false);
}
} // END FactoryCache
private static final class
FactoryManagerCacheKey {
private
ClassLoader cl;
private
Long marker;
private
Object context;
private static final
String KEY =
FactoryFinder.class.
getName() + "." +
FactoryManagerCacheKey.class.
getSimpleName();
public
FactoryManagerCacheKey(
FacesContext facesContext,
ClassLoader cl,
Map<
FactoryManagerCacheKey,
FactoryManager>
factoryMap) {
this.
cl =
cl;
boolean
resolveValueFromFactoryMap = false;
if (null ==
facesContext) {
resolveValueFromFactoryMap = true;
} else {
ExternalContext extContext =
facesContext.
getExternalContext();
context =
extContext.
getContext();
if (null ==
context) {
resolveValueFromFactoryMap = true;
} else {
Map<
String,
Object>
appMap =
extContext.
getApplicationMap();
Long val = (
Long)
appMap.
get(
KEY);
if (null ==
val) {
marker = new
Long(
System.
currentTimeMillis());
appMap.
put(
KEY,
marker);
} else {
marker =
val;
}
}
}
if (
resolveValueFromFactoryMap) {
// 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 cl.
Set<
FactoryManagerCacheKey>
keys =
factoryMap.
keySet();
FactoryManagerCacheKey match = null;
for (
FactoryManagerCacheKey cur :
keys) {
if (this.
cl.
equals(
cur.
cl)) {
if (null !=
cur && null !=
match) {
LOGGER.
log(
Level.
WARNING, "Multiple JSF Applications found on same ClassLoader. Unable to safely determine which FactoryManager instance to use. Defaulting to first match.");
break;
}
match =
cur;
}
}
if (null !=
match) {
this.
marker =
match.
marker;
}
}
}
public
ClassLoader getClassLoader() {
return
cl;
}
public
Object getContext() {
return
context;
}
private
FactoryManagerCacheKey() {}
@
Override
public boolean
equals(
Object obj) {
if (
obj == null) {
return false;
}
if (
getClass() !=
obj.
getClass()) {
return false;
}
final
FactoryManagerCacheKey other = (
FactoryManagerCacheKey)
obj;
if (this.
cl !=
other.
cl && (this.
cl == null || !this.
cl.
equals(
other.
cl))) {
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 + (this.
cl != null ? this.
cl.
hashCode() : 0);
hash = 97 *
hash + (this.
marker != null ? this.
marker.
hashCode() : 0);
return
hash;
}
}
/**
* Maintains the injectionProvider for a single web application.
*/
private static final class
FactoryManager {
private final
Map<
String,
Object>
factories;
private final
Map<
String,
List<
String>>
savedFactoryNames;
private final
ReentrantReadWriteLock lock;
private static final
String INJECTION_PROVIDER_KEY =
FactoryFinder.class.
getPackage().
getName() + "INJECTION_PROVIDER_KEY";
// -------------------------------------------------------- Consturctors
public
FactoryManager() {
lock = new
ReentrantReadWriteLock(true);
factories = new
HashMap<
String,
Object>();
savedFactoryNames = new
HashMap<
String,
List<
String>>();
for (
String name :
FACTORY_NAMES) {
factories.
put(
name, new
ArrayList(4));
}
copyInjectionProviderFromFacesContext();
}
public
FactoryManager(
FactoryManager toCopy) {
lock = new
ReentrantReadWriteLock(true);
factories = new
HashMap<
String,
Object>();
savedFactoryNames = new
HashMap<
String,
List<
String>>();
factories.
putAll(
toCopy.
savedFactoryNames);
copyInjectionProviderFromFacesContext();
}
private void
copyInjectionProviderFromFacesContext() {
InjectionProvider injectionProvider = null;
FacesContext context =
FacesContext.
getCurrentInstance();
if (null !=
context) {
injectionProvider = (
InjectionProvider)
context.
getAttributes().
get("com.sun.faces.config.ConfigManager_INJECTION_PROVIDER_TASK");
}
if (null !=
injectionProvider) {
factories.
put(
INJECTION_PROVIDER_KEY,
injectionProvider);
} else {
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE, "Unable to obtain InjectionProvider from init time FacesContext. Does this container implement the Mojarra Injection SPI?");
}
}
}
// ------------------------------------------------------ Public Methods
public
Collection<
Object>
getFactories() {
return
factories.
values();
}
public void
addFactory(
String factoryName,
String implementation) {
Object result =
factories.
get(
factoryName);
lock.
writeLock().
lock();
try {
if (
result instanceof
List) {
TypedCollections.
dynamicallyCastList((
List)
result,
String.class).
add(0,
implementation);
}
} finally {
lock.
writeLock().
unlock();
}
}
InjectionProvider getInjectionProvider() {
InjectionProvider result = (
InjectionProvider)
factories.
get(
INJECTION_PROVIDER_KEY);
return
result;
}
void
clearInjectionProvider() {
factories.
remove(
INJECTION_PROVIDER_KEY);
}
public
Object getFactory(
ClassLoader cl,
String factoryName) {
Object factoryOrList;
lock.
readLock().
lock();
try {
factoryOrList =
factories.
get(
factoryName);
if (!(
factoryOrList instanceof
List)) {
return
factoryOrList;
}
} finally {
lock.
readLock().
unlock();
}
// factory hasn't been constructed
lock.
writeLock().
lock();
try {
// double check the current value. Another thread
// may have completed the initialization by the time
// this thread entered this block
factoryOrList =
factories.
get(
factoryName);
if (!(
factoryOrList instanceof
List)) {
return
factoryOrList;
}
savedFactoryNames.
put(
factoryName, new
ArrayList((
List)
factoryOrList));
Object factory =
getImplementationInstance(
cl,
factoryName,
(
List)
factoryOrList);
if (
factory == null) {
ResourceBundle rb =
LOGGER.
getResourceBundle();
String message =
rb.
getString("severe.no_factory");
message =
MessageFormat.
format(
message,
factoryName);
if (
LOGGER.
isLoggable(
Level.
SEVERE)) {
LOGGER.
log(
Level.
SEVERE,
message);
}
factory =
FACTORIES_CACHE.
getFallbackFactory(
cl, this,
factoryName);
if (null ==
factory) {
message =
rb.
getString("severe.no_factory_backup_failed");
message =
MessageFormat.
format(
message,
factoryName);
throw new
IllegalStateException(
message);
}
}
// Record and return the new instance
factories.
put(
factoryName,
factory);
return (
factory);
} finally {
lock.
writeLock().
unlock();
}
}
} // END FactoryManager
}