javax.faces.FactoryFinder Maven / Gradle / Ivy
/*
* $Id: FactoryFinder.java,v 1.35 2007/01/29 22:29:08 rlubke Exp $
*/
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://javaserverfaces.dev.java.net/CDDL.html or
* legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* [Name of File] [ver.__] [Date]
*
* Copyright 2005 Sun Microsystems Inc. All Rights Reserved
*/
package javax.faces;
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.logging.Logger;
import java.lang.reflect.Constructor;
/**
* FactoryFinder 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:
*
* - If the JavaServer Faces configuration file bundled into the
*
WEB-INF
directory of the webapp contains a
* factory
entry of the given factory class name, that
* factory is used.
*
* - If the JavaServer Faces configuration files named by the
*
javax.faces.CONFIG_FILES
* ServletContext
init parameter contain any
* factory
entries of the given factory class name, those
* factories are used, with the last one taking precedence.
*
* - If there are any JavaServer Faces configuration files bundled
* into the
META-INF
directory of any jars on the
* ServletContext
's resource paths, the
* factory
entries of the given factory class name in those
* files are used, with the last one taking precedence.
*
* - If a
META-INF/services/{factory-class-name}
resource
* is visible to the web application class loader for the calling
* application (typically as a result 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.
*
* - If none of the above steps yield a match, the JavaServer Faces
* implementation specific class is used.
*
*
*
* If any of the factories 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
* META-INF/services/javax.faces.context.FacesContextFactory
* in a jar on the webapp ClassLoader. Also say this implementation
* provided by the container vendor had a one argument constructor that
* took a FacesContextFactory
instance. The
* FactoryFinder
system would call that one-argument
* constructor, passing the implementation of
* FacesContextFactory
provided by the JavaServer Faces
* implementation.
*
* 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.
*
* 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.
*/
public final class FactoryFinder {
// ----------------------------------------------------------- Constructors
/**
* Package-private constructor to disable instantiation of this class.
*/
FactoryFinder() {
}
// ----------------------------------------------------- Manifest Constants
/**
* The property name for the
* {@link javax.faces.application.ApplicationFactory} class name.
*/
public final static String APPLICATION_FACTORY =
"javax.faces.application.ApplicationFactory";
/**
* The property name for the
* {@link javax.faces.context.FacesContextFactory} class name.
*/
public final static String FACES_CONTEXT_FACTORY =
"javax.faces.context.FacesContextFactory";
/**
* The property name for the
* {@link javax.faces.lifecycle.LifecycleFactory} class name.
*/
public final static String LIFECYCLE_FACTORY =
"javax.faces.lifecycle.LifecycleFactory";
/**
* The property name for the
* {@link javax.faces.render.RenderKitFactory} class name.
*/
public final static String RENDER_KIT_FACTORY =
"javax.faces.render.RenderKitFactory";
// ------------------------------------------------------- Static Variables
/**
* Keys are web application class loaders. Values are factory
* maps for each web application.
*
* For the nested map, the keys are the factoryName, which must
* be one of the *_FACTORY
constants above. Values are
* one of:
*
*
*
* the actual factory class, if {@link getFactory} has been
* called before on this factoryName
*
* An ArrayList
of Strings
* representing the configured implementations of for the
* factoryName.
*
*
*/
@SuppressWarnings({"CollectionWithoutInitialCapacity"})
private static final Map> applicationMaps =
new HashMap>();
/**
* The set of JavaServer Faces factory classes for which the factory
* discovery mechanism is supported.
*/
private static final String[] FACTORY_NAMES = {
APPLICATION_FACTORY,
FACES_CONTEXT_FACTORY,
LIFECYCLE_FACTORY,
RENDER_KIT_FACTORY
};
/**
* Map of Class instances for the our factory names.
*/
private static Map factoryClasses = null;
private static final Logger LOGGER =
Logger.getLogger("javax.faces", "javax.faces.LogStrings");
// --------------------------------------------------------- Public Methods
/**
* Create (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.
*
* @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 factoryName
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 factoryname
* is null
*/
public static Object getFactory(String factoryName)
throws FacesException {
validateFactoryName(factoryName);
// Identify the web application class loader
ClassLoader classLoader = getClassLoader();
synchronized (applicationMaps) {
Map appMap = getApplicationMap();
// assert(null != appMap);
Object factory;
Object factoryOrList = appMap.get(factoryName);
// If this factory has been retrieved before
if (factoryOrList != null && (!(factoryOrList instanceof List))) {
// just return it.
return (factoryOrList);
}
// else, this factory has not been retrieved before; let's
// find it.
factory = getImplementationInstance(classLoader, factoryName,
(List) factoryOrList);
if (null == factory) {
ResourceBundle rb = LOGGER.getResourceBundle();
String message = rb.getString("severe.no_factory");
message = MessageFormat.format(message, factoryName);
throw new IllegalStateException(message);
}
// Record and return the new instance
appMap.put(factoryName, factory);
return (factory);
}
}
/**
* This method will store the argument
* factoryName/implName
mapping in such a way that
* {@link #getFactory} will find this mapping when searching for a
* match.
*
* This method has no effect if getFactory()
has
* already been called looking for a factory for this
* factoryName
.
*
* This method can be used by implementations to store a factory
* mapping while parsing the Faces configuration file
*
* @throws IllegalArgumentException if factoryName
does not
* identify a standard JavaServer Faces factory name
* @throws NullPointerException if factoryname
* is null
*/
public static void setFactory(String factoryName,
String implName) {
validateFactoryName(factoryName);
Object previouslySetFactories;
Map appMap;
synchronized (applicationMaps) {
appMap = getApplicationMap();
// assert(null != appMap);
// Has set or get been called on this factoryName before?
if (null != (previouslySetFactories = appMap.get(factoryName))) {
// Yes. Has get been called on this factoryName before?
// If previouslySetFactories is not a List, get has been
// called.
if (!(previouslySetFactories instanceof List)) {
// take no action.
return;
}
// get has not been called, previouslySetFactories is a
// List
} else {
// No. Create a List for this FactoryName.
//noinspection CollectionWithoutInitialCapacity
previouslySetFactories = new ArrayList();
appMap.put(factoryName, previouslySetFactories);
}
// Put this at the beginning of the list.
(TypedCollections.dynamicallyCastList((List) previouslySetFactories, String.class)).add(0, implName);
}
}
/**
* Release any references to factory instances associated with the
* class loader for the calling web application. This method should be
* called as apart of web application shutdown in a container where the
* JavaServer Faces API classes are part of the container itself, rather
* than being included inside the web application.
*
* @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();
// Release any and all factory instances corresponding to this
// class loader
synchronized (applicationMaps) {
HashMap map = (HashMap) applicationMaps.get(cl);
if (map != null) {
map.clear();
applicationMaps.remove(cl);
}
}
}
// -------------------------------------------------------- Private Methods
/**
* Identify and return the class loader that is associated with the
* calling web application.
*
* @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);
}
/**
* Load and return an instance of the specified implementation
* class using the following algorithm.
*
*
*
* If the argument implementations
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 factoryName
. Instantiate that
* class and save it for return. If the
* implementations
list has only one element, skip
* this step.
*
* Look for a resource called
* /META-INF/services/<factoryName>
. 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 factoryName
. If we have an
* instantiated factory from the previous step and the
* implementing class has a one arg constructor of the type for
* factoryName
, 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.
*
* Treat each remaining element in the
* implementations
list as a fully qualified class name
* of a class implementing factoryName
. If the current
* element has a one arg constructor of the type for
* factoryName
, 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.
*
* Return the saved factory
*
*
*
* @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.
if (null != (curImplClass = getImplNameFromServices(classLoader,
factoryName))) {
result = getImplGivenPreviousImpl(classLoader, factoryName,
curImplClass, 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;
}
/**
* Perform the logic to get the implementation class for the
* second step of {@link getImplementationInstance}.
*/
private static String getImplNameFromServices(ClassLoader classLoader,
String factoryName) {
// Check for a services definition
String result = null;
BufferedReader reader = null;
String resourceName = "META-INF/services/" + factoryName;
InputStream stream = null;
try {
stream = classLoader.getResourceAsStream(resourceName);
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"));
} catch (UnsupportedEncodingException e) {
reader = new BufferedReader(new InputStreamReader(stream));
}
result = reader.readLine();
}
} catch (IOException e) {
} catch (SecurityException e) {
} finally {
if (reader != null) {
try {
reader.close();
} catch (Throwable t) {
;
}
//noinspection UnusedAssignment
reader = null;
}
if (stream != null) {
try {
stream.close();
} catch (Throwable t) {
;
}
//noinspection UnusedAssignment
stream = null;
}
}
return result;
}
/**
* Implement the decorator pattern for the factory
* implementation.
*
* If previousImpl
is non-null
and the
* class named by the argument implName
has a one arg
* contstructor of type factoryName
, instantiate it,
* passing previousImpl to the constructor.
*
* Otherwise, we just instantiate and return
* implName
.
*
* @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-null
, 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;
// if we have a previousImpl and the appropriate one arg ctor.
if ((null != previousImpl) &&
(null != (factoryClass = getFactoryClass(classLoader,
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);
}
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 java.lang.Class
for the argument
* factory.
*/
private static Class getFactoryClass(ClassLoader classLoader,
String factoryClassName) {
if (null == factoryClasses) {
factoryClasses = new HashMap(FACTORY_NAMES.length);
factoryClasses.put(APPLICATION_FACTORY,
javax.faces.application.ApplicationFactory.class);
factoryClasses.put(FACES_CONTEXT_FACTORY,
javax.faces.context.FacesContextFactory.class);
factoryClasses.put(LIFECYCLE_FACTORY,
javax.faces.lifecycle.LifecycleFactory.class);
factoryClasses.put(RENDER_KIT_FACTORY,
javax.faces.render.RenderKitFactory.class);
}
return factoryClasses.get(factoryClassName);
}
/**
* This method must only be called from within a synchronized
* block for the {@link #applicationMaps} ivar.
*/
private static Map getApplicationMap() {
// Identify the web application class loader
ClassLoader classLoader = getClassLoader();
Map result ;
// Return any previously instantiated factory instance (of the
// specified name) for this web application
result = applicationMaps.get(classLoader);
if (result == null) {
//noinspection CollectionWithoutInitialCapacity
result = new HashMap();
applicationMaps.put(classLoader, result);
}
return result;
}
private static void validateFactoryName(String factoryName) {
// Validate the requested factory name
if (factoryName == null) {
throw new NullPointerException();
}
boolean found = false;
for (int i = 0; i < FACTORY_NAMES.length; i++) {
if (factoryName.equals(FACTORY_NAMES[i])) {
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException(factoryName);
}
}
}