javax.faces.FactoryFinder Maven / Gradle / Ivy
/*
* $Id: FactoryFinder.java,v 1.37 2007/05/21 21:23:29 rlubke Exp $
*/
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. 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.html
* or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [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.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Future;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
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;
/**
* 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.ExceptionHandlerFactory} class name.
*/
public final static String EXCEPTION_HANDLER_FACTORY =
"javax.faces.context.ExceptionHandlerFactory";
/**
* The property name for the {@link
* javax.faces.context.ExternalContextFactory} class name.
*/
public final static String EXTERNAL_CONTEXT_FACTORY =
"javax.faces.context.ExternalContextFactory";
/**
* 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.context.PartialViewContextFactory} class name.
*/
public final static String PARTIAL_VIEW_CONTEXT_FACTORY =
"javax.faces.context.PartialViewContextFactory";
/**
* The property name for the {@link
* javax.faces.component.visit.VisitContextFactory} class name.
*/
public final static String VISIT_CONTEXT_FACTORY =
"javax.faces.component.visit.VisitContextFactory";
/**
* 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";
/**
* The property name for the {@link
* javax.faces.view.ViewDeclarationLanguage} class name.
*/
public final static String VIEW_DECLARATION_LANGUAGE_FACTORY =
"javax.faces.view.ViewDeclarationLanguageFactory";
/**
* The property name for the {@link
* javax.faces.view.facelets.TagHandlerDelegate} class name.
*/
public final static String TAG_HANDLER_DELEGATE_FACTORY =
"javax.faces.view.facelets.TagHandlerDelegateFactory";
// ------------------------------------------------------- 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. The entries in this list must be
* alphabetically ordered according to the entire string, not just
* the last part!
*/
private static final String[] FACTORY_NAMES = {
APPLICATION_FACTORY,
VISIT_CONTEXT_FACTORY,
EXCEPTION_HANDLER_FACTORY,
EXTERNAL_CONTEXT_FACTORY,
FACES_CONTEXT_FACTORY,
LIFECYCLE_FACTORY,
VIEW_DECLARATION_LANGUAGE_FACTORY,
PARTIAL_VIEW_CONTEXT_FACTORY,
RENDER_KIT_FACTORY,
TAG_HANDLER_DELEGATE_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.
*
* The standard factories and wrappers
* in JSF all implement the interface {@link FacesWrapper}. If the
* returned Object
is an implementation of one of the
* standard factories, it must be legal to cast it to an instance of
* FacesWrapper
and call {@link
* FacesWrapper#getWrapped} on the instance.
*
* @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 must be called during of
* web application shutdown.
*
* @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(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(VISIT_CONTEXT_FACTORY,
javax.faces.component.visit.VisitContextFactory.class);
factoryClasses.put(LIFECYCLE_FACTORY,
javax.faces.lifecycle.LifecycleFactory.class);
factoryClasses.put(PARTIAL_VIEW_CONTEXT_FACTORY,
javax.faces.context.PartialViewContextFactory.class);
factoryClasses.put(RENDER_KIT_FACTORY,
javax.faces.render.RenderKitFactory.class);
factoryClasses.put(VIEW_DECLARATION_LANGUAGE_FACTORY,
javax.faces.view.ViewDeclarationLanguageFactory.class);
factoryClasses.put(TAG_HANDLER_DELEGATE_FACTORY,
javax.faces.view.facelets.TagHandlerDelegateFactory.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);
}
}
// ----------------------------------------------------------- Inner Classes
/**
* Managed the mappings between a web application and its configured
* factories.
*/
private static final class FactoryManagerCache {
private ConcurrentMap> applicationMap =
new ConcurrentHashMap>();
// ------------------------------------------------------ Public Methods
private FactoryManager getApplicationFactoryManager(ClassLoader cl) {
while (true) {
Future factories = applicationMap.get(cl);
if (factories == null) {
Callable callable =
new Callable() {
public FactoryManager call()
throws Exception {
return new FactoryManager();
}
};
FutureTask ft =
new FutureTask(callable);
factories = applicationMap.putIfAbsent(cl, ft);
if (factories == null) {
factories = ft;
ft.run();
}
}
try {
return factories.get();
} catch (CancellationException ce) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST,
ce.toString(),
ce);
}
applicationMap.remove(cl);
} catch (InterruptedException ie) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST,
ie.toString(),
ie);
}
applicationMap.remove(cl);
} catch (ExecutionException ee) {
throw new FacesException(ee);
}
}
}
public void removeApplicationFactoryManager(ClassLoader cl) {
applicationMap.remove(cl);
}
} // END FactoryCache
/**
* 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
}