blob: 4719049f80e9e8467f42dccbf25cf7cc3284c969 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package javax.faces;
import javax.faces.application.ApplicationFactory;
import javax.faces.component.visit.VisitContextFactory;
import javax.faces.context.ExceptionHandlerFactory;
import javax.faces.context.ExternalContextFactory;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
import javax.faces.context.FlashFactory;
import javax.faces.context.PartialViewContextFactory;
import javax.faces.flow.FlowHandlerFactory;
import javax.faces.lifecycle.ClientWindowFactory;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.render.RenderKitFactory;
import javax.faces.view.ViewDeclarationLanguageFactory;
import javax.faces.view.facelets.FaceletCacheFactory;
import javax.faces.view.facelets.TagHandlerDelegateFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.component.search.SearchExpressionContextFactory;
/**
* see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">JSF Specification</a>
*/
public final class FactoryFinder
{
public static final String APPLICATION_FACTORY = "javax.faces.application.ApplicationFactory";
public static final String EXCEPTION_HANDLER_FACTORY = "javax.faces.context.ExceptionHandlerFactory";
public static final String EXTERNAL_CONTEXT_FACTORY = "javax.faces.context.ExternalContextFactory";
public static final String FACES_CONTEXT_FACTORY = "javax.faces.context.FacesContextFactory";
public static final String LIFECYCLE_FACTORY = "javax.faces.lifecycle.LifecycleFactory";
public static final String PARTIAL_VIEW_CONTEXT_FACTORY = "javax.faces.context.PartialViewContextFactory";
public static final String RENDER_KIT_FACTORY = "javax.faces.render.RenderKitFactory";
public static final String TAG_HANDLER_DELEGATE_FACTORY = "javax.faces.view.facelets.TagHandlerDelegateFactory";
public static final String VIEW_DECLARATION_LANGUAGE_FACTORY = "javax.faces.view.ViewDeclarationLanguageFactory";
public static final String VISIT_CONTEXT_FACTORY = "javax.faces.component.visit.VisitContextFactory";
public static final String FACELET_CACHE_FACTORY = "javax.faces.view.facelets.FaceletCacheFactory";
public static final String FLASH_FACTORY = "javax.faces.context.FlashFactory";
public static final String FLOW_HANDLER_FACTORY = "javax.faces.flow.FlowHandlerFactory";
public static final String CLIENT_WINDOW_FACTORY = "javax.faces.lifecycle.ClientWindowFactory";
public static final String SEARCH_EXPRESSION_CONTEXT_FACTORY =
"javax.faces.component.search.SearchExpressionContextFactory";
private static final Map<String, Class<?>> FACTORY_MAPPING = new HashMap<String, Class<?>>();
private static final ClassLoader MYFACES_CLASSLOADER;
private static final String INJECTION_PROVIDER_INSTANCE = "oam.spi.INJECTION_PROVIDER_KEY";
private static final String INJECTED_BEAN_STORAGE_KEY = "org.apache.myfaces.spi.BEAN_ENTRY_STORAGE";
private static final String BEAN_ENTRY_CLASS_NAME = "org.apache.myfaces.cdi.util.BeanEntry";
private static final Logger LOGGER = Logger.getLogger(FactoryFinder.class.getName());
/**
* used as a monitor for itself and _factories. Maps in this map are used as monitors for themselves and the
* corresponding maps in _factories.
*/
private static Map<ClassLoader, Map<String, List<String>>> registeredFactoryNames
= new HashMap<ClassLoader, Map<String, List<String>>>(5);
/**
* Maps from classLoader to another map, the container (i.e. Tomcat) will create a class loader for each web app
* that it controls (typically anyway) and that class loader is used as the key.
*
* The secondary map maps the factory name (i.e. FactoryFinder.APPLICATION_FACTORY) to actual instances that are
* created via getFactory. The instances will be of the class specified in the setFactory method for the factory
* name, i.e. FactoryFinder.setFactory(FactoryFinder.APPLICATION_FACTORY, MyFactory.class).
*/
private static Map<ClassLoader, Map<String, Object>> factories
= new HashMap<ClassLoader, Map<String, Object>>(5);
static
{
FACTORY_MAPPING.put(APPLICATION_FACTORY, ApplicationFactory.class);
FACTORY_MAPPING.put(EXCEPTION_HANDLER_FACTORY, ExceptionHandlerFactory.class);
FACTORY_MAPPING.put(EXTERNAL_CONTEXT_FACTORY, ExternalContextFactory.class);
FACTORY_MAPPING.put(FACES_CONTEXT_FACTORY, FacesContextFactory.class);
FACTORY_MAPPING.put(LIFECYCLE_FACTORY, LifecycleFactory.class);
FACTORY_MAPPING.put(PARTIAL_VIEW_CONTEXT_FACTORY, PartialViewContextFactory.class);
FACTORY_MAPPING.put(RENDER_KIT_FACTORY, RenderKitFactory.class);
FACTORY_MAPPING.put(TAG_HANDLER_DELEGATE_FACTORY, TagHandlerDelegateFactory.class);
FACTORY_MAPPING.put(VIEW_DECLARATION_LANGUAGE_FACTORY, ViewDeclarationLanguageFactory.class);
FACTORY_MAPPING.put(VISIT_CONTEXT_FACTORY, VisitContextFactory.class);
FACTORY_MAPPING.put(FACELET_CACHE_FACTORY, FaceletCacheFactory.class);
FACTORY_MAPPING.put(FLASH_FACTORY, FlashFactory.class);
FACTORY_MAPPING.put(FLOW_HANDLER_FACTORY, FlowHandlerFactory.class);
FACTORY_MAPPING.put(CLIENT_WINDOW_FACTORY, ClientWindowFactory.class);
FACTORY_MAPPING.put(SEARCH_EXPRESSION_CONTEXT_FACTORY, SearchExpressionContextFactory.class);
try
{
ClassLoader classLoader;
if (System.getSecurityManager() != null)
{
classLoader = (ClassLoader) AccessController.doPrivileged(
(PrivilegedExceptionAction) () -> FactoryFinder.class.getClassLoader());
}
else
{
classLoader = FactoryFinder.class.getClassLoader();
}
if (classLoader == null)
{
throw new FacesException("jsf api class loader cannot be identified", null);
}
MYFACES_CLASSLOADER = classLoader;
}
catch (Exception e)
{
throw new FacesException("jsf api class loader cannot be identified", e);
}
}
// ~ Start FactoryFinderProvider Support
private static Object factoryFinderProviderFactoryInstance;
private static volatile boolean initialized = false;
private static void initializeFactoryFinderProviderFactory()
{
if (!initialized)
{
factoryFinderProviderFactoryInstance = _FactoryFinderProviderFactory.getInstance();
initialized = true;
}
}
// ~ End FactoryFinderProvider Support
// avoid instantiation
FactoryFinder()
{
}
/**
* <p>
* 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.
* </p>
*
* <p>
* The standard factories and wrappers in JSF all implement the interface {@link FacesWrapper}. If the returned
* <code>Object</code> is an implementation of one of the standard factories, 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
*
* @return A per-web-application instance of the appropriate implementation class for the specified JavaServer Faces
* factory class
*
* @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
{
if (factoryName == null)
{
throw new NullPointerException("factoryName may not be null");
}
initializeFactoryFinderProviderFactory();
if (factoryFinderProviderFactoryInstance == null)
{
// Do the typical stuff
return _getFactory(factoryName);
}
else
{
try
{
//Obtain the FactoryFinderProvider instance for this context.
Object ffp = _FactoryFinderProviderFactory
.FACTORY_FINDER_PROVIDER_FACTORY_GET_FACTORY_FINDER_METHOD
.invoke(factoryFinderProviderFactoryInstance, null);
//Call getFactory method and pass the params
return _FactoryFinderProviderFactory
.FACTORY_FINDER_PROVIDER_GET_FACTORY_METHOD.invoke(ffp, factoryName);
}
catch (InvocationTargetException e)
{
Throwable targetException = e.getCause();
if (targetException instanceof NullPointerException)
{
throw (NullPointerException) targetException;
}
else if (targetException instanceof FacesException)
{
throw (FacesException) targetException;
}
else if (targetException instanceof IllegalArgumentException)
{
throw (IllegalArgumentException) targetException;
}
else if (targetException instanceof IllegalStateException)
{
throw (IllegalStateException) targetException;
}
else if (targetException == null)
{
throw new FacesException(e);
}
else
{
throw new FacesException(targetException);
}
}
catch (Exception e)
{
//No Op
throw new FacesException(e);
}
}
}
private static Object _getFactory(String factoryName) throws FacesException
{
ClassLoader classLoader = getClassLoader();
// This code must be synchronized because this could cause a problem when
// using update feature each time of myfaces (org.apache.myfaces.CONFIG_REFRESH_PERIOD)
// In this moment, a concurrency problem could happen
Map<String, List<String>> factoryClassNames = null;
Map<String, Object> factoryMap = null;
synchronized (registeredFactoryNames)
{
factoryClassNames = registeredFactoryNames.get(classLoader);
if (factoryClassNames == null)
{
String message
= "No Factories configured for this Application. This happens if the faces-initialization "
+ "does not work at all - make sure that you properly include all configuration "
+ "settings necessary for a basic faces application "
+ "and that all the necessary libs are included. Also check the logging output of your "
+ "web application and your container for any exceptions!"
+ "\nIf you did that and find nothing, the mistake might be due to the fact "
+ "that you use some special web-containers which "
+ "do not support registering context-listeners via TLD files and "
+ "a context listener is not setup in your web.xml.\n"
+ "A typical config looks like this;\n<listener>\n"
+ " <listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>\n"
+ "</listener>\n";
throw new IllegalStateException(message);
}
if (!factoryClassNames.containsKey(factoryName))
{
throw new IllegalArgumentException("no factory " + factoryName + " configured for this application.");
}
factoryMap = factories.computeIfAbsent(classLoader, k -> new HashMap<>());
}
List beanEntryStorage;
List<String> classNames;
Object factory;
Object injectionProvider;
synchronized (factoryClassNames)
{
beanEntryStorage = (List) factoryMap.computeIfAbsent(INJECTED_BEAN_STORAGE_KEY,
k -> new CopyOnWriteArrayList());
factory = factoryMap.get(factoryName);
if (factory != null)
{
return factory;
}
classNames = factoryClassNames.get(factoryName);
injectionProvider = factoryMap.get(INJECTION_PROVIDER_INSTANCE);
}
if (injectionProvider == null)
{
injectionProvider = getInjectionProvider();
synchronized (factoryClassNames)
{
factoryMap.put(INJECTION_PROVIDER_INSTANCE, injectionProvider);
}
}
// release lock while calling out
factory = newFactoryInstance(FACTORY_MAPPING.get(factoryName),
classNames.iterator(), classLoader, injectionProvider, beanEntryStorage);
synchronized (factoryClassNames)
{
// check if someone else already installed the factory
if (factoryMap.get(factoryName) == null)
{
factoryMap.put(factoryName, factory);
}
}
return factory;
}
private static Object getInjectionProvider()
{
try
{
// Remember the first call in a webapp over FactoryFinder.getFactory(...) comes in the
// initialization block, so there is a startup FacesContext active and
// also a valid startup ExternalContext. Note after that, we need to cache
// the injection provider for the classloader, because in a normal
// request there is no active FacesContext in the moment and this call will
// surely fail.
FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext != null)
{
Object injectionProviderFactory =
_FactoryFinderProviderFactory.INJECTION_PROVIDER_FACTORY_GET_INSTANCE_METHOD
.invoke(_FactoryFinderProviderFactory.INJECTION_PROVIDER_CLASS);
Object injectionProvider =
_FactoryFinderProviderFactory.INJECTION_PROVIDER_FACTORY_GET_INJECTION_PROVIDER_METHOD
.invoke(injectionProviderFactory, facesContext.getExternalContext());
return injectionProvider;
}
}
catch (Exception e)
{
}
return null;
}
private static void injectAndPostConstruct(Object injectionProvider, Object instance, List injectedBeanStorage)
{
if (injectionProvider != null)
{
try
{
Object creationMetaData = _FactoryFinderProviderFactory.INJECTION_PROVIDER_INJECT_METHOD.invoke(
injectionProvider, instance);
addBeanEntry(instance, creationMetaData, injectedBeanStorage);
_FactoryFinderProviderFactory.INJECTION_PROVIDER_POST_CONSTRUCT_METHOD.invoke(
injectionProvider, instance, creationMetaData);
}
catch (Exception ex)
{
throw new FacesException(ex);
}
}
}
private static void preDestroy(Object injectionProvider, Object beanEntry)
{
if (injectionProvider != null)
{
try
{
_FactoryFinderProviderFactory.INJECTION_PROVIDER_PRE_DESTROY_METHOD.invoke(
injectionProvider, getInstance(beanEntry), getCreationMetaData(beanEntry));
}
catch (Exception ex)
{
throw new FacesException(ex);
}
}
}
private static Object getInstance(Object beanEntry)
{
try
{
Method getterMethod = getMethod(beanEntry, "getInstance");
return getterMethod.invoke(beanEntry);
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
private static Object getCreationMetaData(Object beanEntry)
{
try
{
Method getterMethod = getMethod(beanEntry, "getCreationMetaData");
return getterMethod.invoke(beanEntry);
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
}
private static Method getMethod(Object beanEntry, String methodName) throws NoSuchMethodException
{
return beanEntry.getClass().getDeclaredMethod(methodName);
}
private static void addBeanEntry(Object instance, Object creationMetaData, List injectedBeanStorage)
{
try
{
Class<?> beanEntryClass = _FactoryFinderProviderFactory.classForName(BEAN_ENTRY_CLASS_NAME);
Constructor beanEntryConstructor = beanEntryClass.getDeclaredConstructor(Object.class, Object.class);
Object result = beanEntryConstructor.newInstance(instance, creationMetaData);
injectedBeanStorage.add(result);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private static Object newFactoryInstance(Class<?> interfaceClass, Iterator<String> classNamesIterator,
ClassLoader classLoader, Object injectionProvider,
List injectedBeanStorage)
{
try
{
Object current = null;
while (classNamesIterator.hasNext())
{
String implClassName = classNamesIterator.next();
Class<?> implClass = null;
try
{
implClass = classLoader.loadClass(implClassName);
}
catch (ClassNotFoundException e)
{
implClass = MYFACES_CLASSLOADER.loadClass(implClassName);
}
// check, if class is of expected interface type
if (!interfaceClass.isAssignableFrom(implClass))
{
throw new IllegalArgumentException("Class " + implClassName + " is no " + interfaceClass.getName());
}
if (current == null)
{
// nothing to decorate
current = implClass.newInstance();
injectAndPostConstruct(injectionProvider, current, injectedBeanStorage);
}
else
{
// let's check if class supports the decorator pattern
try
{
Constructor<?> delegationConstructor = implClass.getConstructor(new Class[] { interfaceClass });
// impl class supports decorator pattern,
try
{
// create new decorator wrapping current
current = delegationConstructor.newInstance(new Object[] { current });
injectAndPostConstruct(injectionProvider, current, injectedBeanStorage);
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException e)
{
throw new FacesException(e);
}
}
catch (NoSuchMethodException e)
{
// no decorator pattern support
current = implClass.newInstance();
injectAndPostConstruct(injectionProvider, current, injectedBeanStorage);
}
}
}
return current;
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e)
{
throw new FacesException(e);
}
}
public static void setFactory(String factoryName, String implName)
{
if (factoryName == null)
{
throw new NullPointerException("factoryName may not be null");
}
initializeFactoryFinderProviderFactory();
if (factoryFinderProviderFactoryInstance == null)
{
// Do the typical stuff
_setFactory(factoryName, implName);
}
else
{
try
{
//Obtain the FactoryFinderProvider instance for this context.
Object ffp = _FactoryFinderProviderFactory
.FACTORY_FINDER_PROVIDER_FACTORY_GET_FACTORY_FINDER_METHOD
.invoke(factoryFinderProviderFactoryInstance, null);
//Call getFactory method and pass the params
_FactoryFinderProviderFactory
.FACTORY_FINDER_PROVIDER_SET_FACTORY_METHOD.invoke(ffp, factoryName, implName);
}
catch (InvocationTargetException e)
{
Throwable targetException = e.getCause();
if (targetException instanceof NullPointerException)
{
throw (NullPointerException) targetException;
}
else if (targetException instanceof FacesException)
{
throw (FacesException) targetException;
}
else if (targetException instanceof IllegalArgumentException)
{
throw (IllegalArgumentException) targetException;
}
else if (targetException == null)
{
throw new FacesException(e);
}
else
{
throw new FacesException(targetException);
}
}
catch (Exception e)
{
//No Op
throw new FacesException(e);
}
}
}
private static void _setFactory(String factoryName, String implName)
{
checkFactoryName(factoryName);
ClassLoader classLoader = getClassLoader();
Map<String, List<String>> factoryClassNames = null;
synchronized (registeredFactoryNames)
{
Map<String, Object> factories = FactoryFinder.factories.get(classLoader);
if (factories != null && factories.containsKey(factoryName))
{
// Javadoc says ... This method has no effect if getFactory() has already been
// called looking for a factory for this factoryName.
return;
}
factoryClassNames = registeredFactoryNames.computeIfAbsent(classLoader,
k -> new HashMap<>());
}
synchronized (factoryClassNames)
{
List<String> classNameList = factoryClassNames.computeIfAbsent(factoryName, k -> new ArrayList<>());
classNameList.add(implName);
}
}
public static void releaseFactories() throws FacesException
{
initializeFactoryFinderProviderFactory();
if (factoryFinderProviderFactoryInstance == null)
{
// Do the typical stuff
_releaseFactories();
}
else
{
try
{
//Obtain the FactoryFinderProvider instance for this context.
Object ffp = _FactoryFinderProviderFactory
.FACTORY_FINDER_PROVIDER_FACTORY_GET_FACTORY_FINDER_METHOD
.invoke(factoryFinderProviderFactoryInstance, null);
//Call getFactory method and pass the params
_FactoryFinderProviderFactory.FACTORY_FINDER_PROVIDER_RELEASE_FACTORIES_METHOD.invoke(ffp, null);
}
catch (InvocationTargetException e)
{
Throwable targetException = e.getCause();
if (targetException instanceof FacesException)
{
throw (FacesException) targetException;
}
else if (targetException == null)
{
throw new FacesException(e);
}
else
{
throw new FacesException(targetException);
}
}
catch (Exception e)
{
//No Op
throw new FacesException(e);
}
}
}
private static void _releaseFactories() throws FacesException
{
ClassLoader classLoader = getClassLoader();
Map<String, Object> factoryMap;
// This code must be synchronized
synchronized (registeredFactoryNames)
{
factoryMap = factories.remove(classLoader);
// _registeredFactoryNames has as value type Map<String,List> and this must
// be cleaned before release (for gc).
Map<String, List<String>> factoryClassNames = registeredFactoryNames.get(classLoader);
if (factoryClassNames != null)
{
factoryClassNames.clear();
}
registeredFactoryNames.remove(classLoader);
}
if (factoryMap != null)
{
Object injectionProvider = factoryMap.remove(INJECTION_PROVIDER_INSTANCE);
if (injectionProvider != null)
{
List injectedBeanStorage = (List)factoryMap.get(INJECTED_BEAN_STORAGE_KEY);
FacesException firstException = null;
for (Object entry : injectedBeanStorage)
{
try
{
preDestroy(injectionProvider, entry);
}
catch (FacesException e)
{
LOGGER.log(Level.SEVERE, "#preDestroy failed", e);
if (firstException == null)
{
firstException = e; //all preDestroy callbacks need to get invoked
}
}
}
injectedBeanStorage.clear();
if (firstException != null)
{
throw firstException;
}
}
}
}
private static void checkFactoryName(String factoryName)
{
if (!FACTORY_MAPPING.containsKey(factoryName))
{
throw new IllegalArgumentException("factoryName '" + factoryName + '\'');
}
}
private static ClassLoader getClassLoader()
{
try
{
ClassLoader classLoader = null;
if (System.getSecurityManager() != null)
{
classLoader = (ClassLoader) AccessController.doPrivileged(
(PrivilegedExceptionAction) () -> Thread.currentThread().getContextClassLoader());
}
else
{
classLoader = Thread.currentThread().getContextClassLoader();
}
if (classLoader == null)
{
throw new FacesException("web application class loader cannot be identified", null);
}
return classLoader;
}
catch (Exception e)
{
throw new FacesException("web application class loader cannot be identified", e);
}
}
}