javax.faces.FactoryFinder Maven / Gradle / Ivy
Show all versions of jsf-api Show documentation
/*
* $Id: FactoryFinder.java,v 1.37.4.2 2011/04/21 21:54:12 edburns Exp $
*/
/*
* 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 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.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
/**
* 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
private static final FactoryManagerCache FACTORIES_CACHE =
new FactoryManagerCache();
/**
* 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");
// Ensure the factory names are sorted.
//
static {
Arrays.sort(FACTORY_NAMES);
}
// --------------------------------------------------------- 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();
FactoryManager manager =
FACTORIES_CACHE.getApplicationFactoryManager(classLoader);
return manager.getFactory(classLoader, factoryName);
}
/**
* 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);
// Identify the web application class loader
ClassLoader classLoader = getClassLoader();
FactoryManager manager =
FACTORIES_CACHE.getApplicationFactoryManager(classLoader);
manager.addFactory(factoryName, 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();
FACTORIES_CACHE.removeApplicationFactoryManager(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.
List 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;
}
/**
* Perform the logic to get the implementation class for the
* second step of {@link FactoryFinder#getImplementationInstance(ClassLoader, String, java.util.List)}.
*/
private static List getImplNameFromServices(ClassLoader classLoader,
String factoryName) {
// Check for a services definition
List result = null;
String resourceName = "META-INF/services/" + factoryName;
InputStream stream;
BufferedReader reader = null;
try {
Enumeration 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(3);
}
result.add(reader.readLine());
} catch (UnsupportedEncodingException uee) {
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;
}
/**
* 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(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(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);
}
/**
* 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
* result.
*/
private static final class FactoryManagerCache {
private ConcurrentMap applicationMap =
new ConcurrentHashMap();
private AtomicBoolean logNullFacesContext = new AtomicBoolean(false);
private AtomicBoolean logNonNullFacesContext = new AtomicBoolean(false);
// ------------------------------------------------------ Public Methods
private FactoryManager getApplicationFactoryManager(ClassLoader cl) {
FacesContext facesContext = FacesContext.getCurrentInstance();
boolean isSpecialInitializationCase = detectSpecialInitializationCase(facesContext);
FactoryManagerCacheKey key = new FactoryManagerCacheKey(facesContext,
cl, applicationMap);
FactoryManager result = applicationMap.get(key);
if (result == null) {
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 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;
}
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 = 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) {
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 factoryMap) {
this.cl = cl;
if (null != facesContext) {
ExternalContext extContext = facesContext.getExternalContext();
context = extContext.getContext();
Map appMap = extContext.getApplicationMap();
Long val = (Long) appMap.get(KEY);
if (null == val) {
marker = new Long(System.currentTimeMillis());
appMap.put(KEY, marker);
} else {
marker = val;
}
} else {
// 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 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 factories for a single web application.
*/
private static final class FactoryManager {
private final Map factories;
private final ReentrantReadWriteLock lock;
// -------------------------------------------------------- Consturctors
public FactoryManager() {
factories = new HashMap();
for (String name : FACTORY_NAMES) {
factories.put(name, new ArrayList(4));
}
lock = new ReentrantReadWriteLock(true);
}
// ------------------------------------------------------ Public Methods
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();
}
}
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;
}
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);
throw new IllegalStateException(message);
}
// Record and return the new instance
factories.put(factoryName, factory);
return (factory);
} finally {
lock.writeLock().unlock();
}
}
} // END FactoryManager
}