blob: 098964b06c8109d1235c77084dbd5d45d6978b6c [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 org.apache.myfaces.spi;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.myfaces.util.lang.ClassUtils;
/**
* <p>{@link javax.faces.FactoryFinder} is a class with three methods:</p>
*
* <code>
* public final class FactoryFinder
* {
* public static Object getFactory(String factoryName) throws FacesException {...}
* public static void setFactory(String factoryName, String implName) {...}
* public static void releaseFactories() throws FacesException {...}
* }
* </code>
*
* <p>The javadoc describe the intention of FactoryFinder class:
* </p>
*
* <p>"... 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...."</p>
*
* <p>In few words, this class allows to find JSF factory classes. The necessary
* information to create factory instances is loaded on initialization time,
* but which locations contains such information (for more information see
* JSF 2.0 spec section 11.4.2) (here the only interest is in jsf factories
* initialization information) ?</p>
*
* <ul>
* <li>Look factories on META-INF/services/[factoryClassName]</li>
* <li>Look META-INF/faces-config.xml or META-INF/[prefix].faces-config.xml</li>
* <li>Look the files pointed by javax.faces.CONFIG_FILES web config param
* (note WEB-INF/web.xml is taken into consideration)</li>
* <li>Look the applicationFacesConfig on WEB-INF/faces-config.xml</li>
* </ul>
*
* <p>Based on the previous facts, the first conclusion to take into account arise:
* Configuration information is gathered per "web context". What is a "web context"?
* In simple terms, is the "space" where a web application is deployed.
* Let's suppose an EAR file with two WAR files: a.war and b.war.
* Both contains different "web applications" and when are deployed has
* different "web context", so both can provide different factory configuration,
* because both has different WEB-INF/web.xml and WEB-INF/faces-config.xml files.</p>
*
* <p>Now, given a request, how the web container identify a "web context"?
* At start, it receives the request information and based on that it decides
* which web application should process it. After that, it assign to a thread
* from is thread pool to be processed and the control is passed to the proper
* filters/servlets.</p>
*
* <p>So, if there is not a servlet context/portlet context/whatever context,
* how to identify a "web context"? The answer is using the thread, but the one
* who knows how to do that is the web container, not the jsf implementation.</p>
*
* <p>The existing problem is caused by a "shortcut" taken to make things easier.
* Instead use the current "thread", it is taken as advantage the fact that each
* web application deployed has a different classloader. That is true for a lot
* of application servers, so the current implementation of FactoryFinder is based
* on that fact too and has worked well since the beginning.</p>
*
* <p>Now let's examine in detail how a "single classloader per EAR" option could
* work. If the EAR has two WAR files (a.war and b.war), we have two web context,
* and the initialization code is executed twice. When all FactoryFinder methods
* are called?</p>
*
* <ul>
* <li>FactoryFinder.setFactory is called on initialization</li>
* <li>FactoryFinder.releaseFactories is called on shutdown</li>
* <li>FactoryFinder.getFactory is called after initialization configuration is
* done but before shutdown call to FactoryFinder.setFactory </li>
* </ul>
*
* <p>Remember all methods of FactoryFinder are static.</p>
*
* <p>One possible solution could be:</p>
*
* <ol>
* <li>Create a class called FactoryFinderProvider, that has the same three method
* but in a non static version.</li>
* <li>A singleton component is provided that holds the information of the
* FactoryFinderProviderFactory. This one works per classloader, so the
* singleton is implemented using an static variable. To configure it, the
* static method should be called when the "classloader realm" is initialized,
* before any web context is started (the WAR is deployed). Usually the EAR is
* started as a single entity, so this should occur when the EAR starts, but
* before the WAR files are started (or the web context are created).
* The singleton will be responsible to decide which FactoryFinderProvider
* should be used, based on the current thread information.</li>
* <li>Add utility methods to retrieve the required objects and call the methods
* using reflection from javax.faces.FactoryFinder</li>
* </ol>
*
* <p>This class implements the proposed solution. Note by definition, this factory
* cannot be configured using SPI standard algorithm (look for
* META-INF/services/[factory_class_name]).</p>
*
* @since 2.0.5
* @author Leonardo Uribe
*
*/
public abstract class FactoryFinderProviderFactory
{
private static volatile FactoryFinderProviderFactory instance = null;
/**
* Set the instance to be used by {@link javax.faces.FactoryFinder} to resolve
* factories.
*
* <p>This method should be called before any "web context" is initialized in the
* current "classloader context". For example, if a EAR file contains two WAR files,
* this method should be called before initialize any WAR, since each one requires
* a different "web context"</p>
*
* @param instance
*/
public static void setInstance(FactoryFinderProviderFactory instance)
{
// Now we need to make sure the volatile var FactoryFinder._initialized is
// set to false, to make sure the right factory is fetched after this method
// exists. It is just a fail-safe, because after all if the conditions to make
// this call are met, _initialized should be false.
try
{
Class clazz = ClassUtils.classForName("javax.faces.FactoryFinder");
Field field = clazz.getDeclaredField("initialized");
field.setAccessible(true);
if (field.getBoolean(null))
{
Logger log = Logger.getLogger(FactoryFinderProviderFactory.class.getName());
if (log.isLoggable(Level.WARNING))
{
log.log(Level.WARNING,
"Called FactoryFinderProviderFactory.setFactory after " +
"initialized FactoryFinder (first call to getFactory() or setFactory()). " +
"This method should be called before " +
"any 'web context' is initialized in the current 'classloader context'. " +
"By that reason it will not be changed.");
}
}
else
{
FactoryFinderProviderFactory.instance = instance;
}
field.setBoolean(null, false);
}
catch (Exception e)
{
// No Op
Logger log = Logger.getLogger(FactoryFinderProviderFactory.class.getName());
if (log.isLoggable(Level.FINE))
{
log.log(Level.FINE, "Cannot access field _initialized"
+ "from FactoryFinder ", e);
}
}
}
/**
* Retrieve the installed instance of this class to be used by
* {@link javax.faces.FactoryFinder}. If no factory is set, return null
*
* @return
*/
public static FactoryFinderProviderFactory getInstance()
{
return instance;
}
/**
* Provide the FactoryFinderProvider to be used to resolve factories.
* Subclasses must implement this method.
*
* @return
*/
public abstract FactoryFinderProvider getFactoryFinderProvider();
}