View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.spi;
20  
21  import java.lang.reflect.Field;
22  import java.util.logging.Level;
23  import java.util.logging.Logger;
24  
25  import org.apache.myfaces.shared.util.ClassUtils;
26  
27  /**
28   * <p>{@link javax.faces.FactoryFinder} is a class with three methods:</p>
29   * 
30   * <code>
31   * public final class FactoryFinder
32   * {
33   *    public static Object getFactory(String factoryName) throws FacesException {...}
34   *    public static void setFactory(String factoryName, String implName) {...}
35   *    public static void releaseFactories() throws FacesException {...}
36   * }
37   * </code>
38   * 
39   * <p>The javadoc describe the intention of FactoryFinder class:
40   * </p>
41   * 
42   * <p>"... FactoryFinder implements the standard discovery algorithm for all factory 
43   * objects specified in the JavaServer Faces APIs. For a given factory class name, a 
44   * corresponding implementation class is searched for based on the following 
45   * algorithm...."</p>
46   * 
47   * <p>In few words, this class allows to find JSF factory classes. The necessary 
48   * information to create factory instances is loaded on initialization time, 
49   * but which locations contains such information (for more information see 
50   * JSF 2.0 spec section 11.4.2) (here the only interest is in jsf factories 
51   * initialization information) ?</p>
52   * 
53   * <ul>
54   * <li>Look factories on META-INF/services/[factoryClassName]</li>
55   * <li>Look META-INF/faces-config.xml or META-INF/[prefix].faces-config.xml</li>
56   * <li>Look the files pointed by javax.faces.CONFIG_FILES web config param 
57   *     (note WEB-INF/web.xml is taken into consideration)</li>
58   * <li>Look the applicationFacesConfig on WEB-INF/faces-config.xml</li>
59   * </ul>
60   * 
61   * <p>Based on the previous facts, the first conclusion to take into account arise: 
62   * Configuration information is gathered per "web context". What is a "web context"? 
63   * In simple terms, is the "space" where a web application is deployed. 
64   * Let's suppose an EAR file with two WAR files: a.war and b.war. 
65   * Both contains different "web applications" and when are deployed has 
66   * different "web context", so both can provide different factory configuration, 
67   * because both has different WEB-INF/web.xml and WEB-INF/faces-config.xml files.</p>
68   * 
69   * <p>Now, given a request, how the web container identify a "web context"? 
70   * At start, it receives the request information and based on that it decides 
71   * which web application should process it. After that, it assign to a thread 
72   * from is thread pool to be processed and the control is passed to the proper 
73   * filters/servlets.</p> 
74   * 
75   * <p>So, if there is not a servlet context/portlet context/whatever context, 
76   * how to identify a "web context"? The answer is using the thread, but the one 
77   * who knows how to do that is the web container, not the jsf implementation.</p>
78   * 
79   * <p>The existing problem is caused by a "shortcut" taken to make things easier. 
80   * Instead use the current "thread", it is taken as advantage the fact that each 
81   * web application deployed has a different classloader. That is true for a lot 
82   * of application servers, so the current implementation of FactoryFinder is based 
83   * on that fact too and has worked well since the beginning.</p>
84   * 
85   * <p>Now let's examine in detail how a "single classloader per EAR" option could 
86   * work. If the EAR has two WAR files (a.war and b.war), we have two web context, 
87   * and the initialization code is executed twice. When all FactoryFinder methods 
88   * are called?</p>
89   * 
90   * <ul>
91   * <li>FactoryFinder.setFactory is called on initialization</li>
92   * <li>FactoryFinder.releaseFactories is called on shutdown</li>
93   * <li>FactoryFinder.getFactory is called after initialization configuration is 
94   *     done but before shutdown call to FactoryFinder.setFactory </li>
95   * </ul>
96   * 
97   * <p>Remember all methods of FactoryFinder are static.</p> 
98   * 
99   * <p>One possible solution could be:</p>
100  * 
101  * <ol>
102  * <li>Create a class called FactoryFinderProvider, that has the same three method 
103  *     but in a non static version.</li>
104  * <li>A singleton component is provided that holds the information of the 
105  *     FactoryFinderProviderFactory. This one works per classloader, so the 
106  *     singleton is implemented using an static variable. To configure it, the 
107  *     static method should be called when the "classloader realm" is initialized, 
108  *     before any web context is started (the WAR is deployed). Usually the EAR is 
109  *     started as a single entity, so this should occur when the EAR starts, but 
110  *     before the WAR files are started (or the web context are created). 
111  *     The singleton will be responsible to decide which FactoryFinderProvider 
112  *     should be used, based on the current thread information.</li>
113  * <li>Add utility methods to retrieve the required objects and call the methods 
114  *     using reflection from javax.faces.FactoryFinder</li>
115  * </ol>
116  * 
117  * <p>This class implements the proposed solution. Note by definition, this factory 
118  * cannot be configured using SPI standard algorithm (look for 
119  * META-INF/services/[factory_class_name]).</p>
120  * 
121  * @since 2.0.5
122  * @author Leonardo Uribe
123  *
124  */
125 public abstract class FactoryFinderProviderFactory
126 {
127     private static volatile FactoryFinderProviderFactory instance = null;
128     
129     /**
130      * Set the instance to be used by {@link javax.faces.FactoryFinder} to resolve
131      * factories. 
132      * 
133      * <p>This method should be called before any "web context" is initialized in the
134      * current "classloader context". For example, if a EAR file contains two WAR files,
135      * this method should be called before initialize any WAR, since each one requires
136      * a different "web context"</p>
137      * 
138      * @param instance
139      */
140     public static void setInstance(FactoryFinderProviderFactory instance)
141     {
142         
143         
144         // Now we need to make sure the volatile var FactoryFinder._initialized is
145         // set to false, to make sure the right factory is fetched after this method
146         // exists. It is just a fail-safe, because after all if the conditions to make 
147         // this call are met, _initialized should be false.
148         try
149         {
150             Class clazz = ClassUtils.classForName("javax.faces.FactoryFinder");
151             Field field = clazz.getDeclaredField("initialized");
152             field.setAccessible(true);
153             
154             if (field.getBoolean(null))
155             {
156                 Logger log = Logger.getLogger(FactoryFinderProviderFactory.class.getName());
157                 if (log.isLoggable(Level.WARNING))
158                 {
159                     log.log(Level.WARNING,
160                             "Called FactoryFinderProviderFactory.setFactory after " +
161                                     "initialized FactoryFinder (first call to getFactory() or setFactory()). " +
162                                     "This method should be called before " +
163                                     "any 'web context' is initialized in the current 'classloader context'. " +
164                                     "By that reason it will not be changed.");
165                 }
166             }
167             else
168             {
169                 FactoryFinderProviderFactory.instance = instance;
170             }
171             
172             field.setBoolean(null, false);
173         }
174         catch (Exception e)
175         {
176             // No Op
177             Logger log = Logger.getLogger(FactoryFinderProviderFactory.class.getName());
178             if (log.isLoggable(Level.FINE))
179             {
180                 log.log(Level.FINE, "Cannot access field _initialized"
181                         + "from FactoryFinder ", e);
182             }
183         }
184     }
185     
186     /**
187      * Retrieve the installed instance of this class to be used by 
188      * {@link javax.faces.FactoryFinder}. If no factory is set, return null
189      * 
190      * @return
191      */
192     public static FactoryFinderProviderFactory getInstance()
193     {
194         return instance;
195     }
196 
197     /**
198      * Provide the FactoryFinderProvider to be used to resolve factories.
199      * Subclasses must implement this method. 
200      * 
201      * @return
202      */
203     public abstract FactoryFinderProvider getFactoryFinderProvider();
204 }