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.ee;
20  
21  import java.net.URL;
22  import java.util.Arrays;
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  
30  import javax.faces.application.ResourceDependencies;
31  import javax.faces.application.ResourceDependency;
32  import javax.faces.bean.ApplicationScoped;
33  import javax.faces.bean.CustomScoped;
34  import javax.faces.bean.ManagedBean;
35  import javax.faces.bean.ManagedProperty;
36  import javax.faces.bean.NoneScoped;
37  import javax.faces.bean.ReferencedBean;
38  import javax.faces.bean.RequestScoped;
39  import javax.faces.bean.SessionScoped;
40  import javax.faces.bean.ViewScoped;
41  import javax.faces.component.FacesComponent;
42  import javax.faces.component.UIComponent;
43  import javax.faces.component.behavior.FacesBehavior;
44  import javax.faces.context.ExternalContext;
45  import javax.faces.convert.Converter;
46  import javax.faces.convert.FacesConverter;
47  import javax.faces.event.ListenerFor;
48  import javax.faces.event.ListenersFor;
49  import javax.faces.event.NamedEvent;
50  import javax.faces.model.FacesDataModel;
51  import javax.faces.render.FacesBehaviorRenderer;
52  import javax.faces.render.FacesRenderer;
53  import javax.faces.render.Renderer;
54  import javax.faces.validator.FacesValidator;
55  import javax.faces.validator.Validator;
56  import javax.faces.webapp.FacesServlet;
57  import javax.servlet.Servlet;
58  import javax.servlet.ServletContainerInitializer;
59  import javax.servlet.ServletContext;
60  import javax.servlet.ServletException;
61  import javax.servlet.ServletRegistration;
62  import javax.servlet.annotation.HandlesTypes;
63  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
64  
65  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
66  import org.apache.myfaces.shared.config.MyfacesConfig;
67  import org.apache.myfaces.shared_impl.webapp.webxml.DelegatedFacesServlet;
68  import org.apache.myfaces.spi.FacesConfigResourceProvider;
69  import org.apache.myfaces.spi.FacesConfigResourceProviderFactory;
70  import org.apache.myfaces.webapp.ManagedBeanDestroyerListener;
71  
72  /**
73   * This class is called by any Java EE 6 complaint container at startup.
74   * It checks if the current webapp is a JSF-webapp by checking if some of 
75   * the JSF related annotations are specified in the webapp classpath or if
76   * the faces-config.xml file is present. If so, the listener checks if 
77   * the FacesServlet has already been defined in web.xml and if not, it adds
78   * the FacesServlet with the mappings (/faces/*, *.jsf, *.faces) dynamically.
79   * 
80   * @author Jakob Korherr (latest modification by $Author$)
81   * @version $Revision$ $Date$
82   */
83  @HandlesTypes({
84          ApplicationScoped.class,
85          CustomScoped.class,
86          FacesBehavior.class,
87          FacesBehaviorRenderer.class,
88          FacesComponent.class,
89          FacesConverter.class,
90          FacesRenderer.class,
91          FacesValidator.class,
92          FacesDataModel.class,
93          ListenerFor.class,
94          ListenersFor.class,
95          ManagedBean.class,
96          ManagedProperty.class,
97          NamedEvent.class,
98          NoneScoped.class,
99          ReferencedBean.class,
100         RequestScoped.class,
101         ResourceDependencies.class,
102         ResourceDependency.class,
103         SessionScoped.class,
104         ViewScoped.class,
105         UIComponent.class,
106         Converter.class,
107         Renderer.class,
108         Validator.class
109     })
110 public class MyFacesContainerInitializer implements ServletContainerInitializer
111 {
112 
113     /**
114      * If the servlet mapping for the FacesServlet is added dynamically, Boolean.TRUE 
115      * is stored under this key in the ServletContext.
116      */
117     public static final String FACES_SERVLET_ADDED_ATTRIBUTE = "org.apache.myfaces.DYNAMICALLY_ADDED_FACES_SERVLET";
118 
119     /**
120      * If the servlet mapping for the FacesServlet is found on the ServletContext, Boolean.TRUE 
121      * is stored under this key in the ServletContext.
122      */
123     public static final String FACES_SERVLET_FOUND = "org.apache.myfaces.FACES_SERVLET_FOUND";
124     
125     private static final String INITIALIZE_ALWAYS_STANDALONE = "org.apache.myfaces.INITIALIZE_ALWAYS_STANDALONE";
126     
127     /**
128      * If the flag is true, the algoritm skip jar scanning for faces-config files to check if the current
129      * application requires FacesServlet to be added dynamically (servlet spec 3). This param can be set using 
130      * a system property with the same name too.
131      */
132     @JSFWebConfigParam(since="2.2.10", expectedValues = "true, false", defaultValue = "false", 
133             tags = "performance")
134     private static final String INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN = 
135             "org.apache.myfaces.INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN";
136     
137     private static final String FACES_CONFIG_RESOURCE = "/WEB-INF/faces-config.xml";
138     private static final Logger log = Logger.getLogger(MyFacesContainerInitializer.class.getName());
139     private static final String[] FACES_SERVLET_MAPPINGS = { "/faces/*", "*.jsf", "*.faces" };
140     private static final String[] FACES_SERVLET_FULL_MAPPINGS = { "/faces/*", "*.jsf", "*.faces", "*.xhtml" };
141     private static final String FACES_SERVLET_NAME = "FacesServlet";
142     private static final Class<? extends Servlet> FACES_SERVLET_CLASS = FacesServlet.class;
143     private static final Class<?> DELEGATED_FACES_SERVLET_CLASS = DelegatedFacesServlet.class;
144 
145     @Override
146     public void onStartup(Set<Class<?>> clazzes, ServletContext servletContext) throws ServletException
147     {
148         log.log(Level.INFO, "Using " + MyFacesContainerInitializer.class.getName());
149         
150         // No MyfacesConfig available yet, we must read the parameter directly:
151         String supportManagedBeans =
152                 servletContext.getInitParameter(MyfacesConfig.INIT_PARAM_SUPPORT_MANAGED_BEANS);
153         if (supportManagedBeans == null || Boolean.TRUE.toString().equals(supportManagedBeans))
154         {
155             ManagedBeanDestroyerListener destroyListener = new ManagedBeanDestroyerListener();
156             servletContext.addListener(destroyListener);
157 
158             // Publishes the ManagedBeanDestroyerListener instance into the servletContext.
159             // This allows the FacesConfigurator to access the instance and to set the
160             // correct ManagedBeanDestroyer instance on it.
161             // in < 2.3 it the ExternalContext#applicationMap was used but we have no access here
162             servletContext.setAttribute(ManagedBeanDestroyerListener.APPLICATION_MAP_KEY, destroyListener);
163         }
164 
165         boolean startDireclty = shouldStartupRegardless(servletContext);
166         if (startDireclty)
167         {
168             // if the INITIALIZE_ALWAYS_STANDALONE param was set to true,
169             // we do not want to have the FacesServlet being added, we simply 
170             // do no extra configuration in here.
171             return;
172         }
173 
174         // Check for one or more of this conditions:
175         // 1. A faces-config.xml file is found in WEB-INF
176         // 2. A faces-config.xml file is found in the META-INF directory of a jar in the application's classpath.
177         // 3. A filename ending in .faces-config.xml is found in the META-INF directory of a jar in the 
178         //    application's classpath.
179         // 4. The javax.faces.CONFIG_FILES context param is declared in web.xml or web-fragment.xml.
180         // 5. The Set of classes passed to the onStartup() method of the ServletContainerInitializer 
181         //    implementation is not empty.
182         if ((clazzes != null && !clazzes.isEmpty()) || isFacesConfigPresent(servletContext))
183         {
184             // look for the FacesServlet
185             Map<String, ? extends ServletRegistration> servlets = servletContext.getServletRegistrations();
186             for (Map.Entry<String, ? extends ServletRegistration> servletEntry : servlets.entrySet())
187             {
188                 String className = servletEntry.getValue().getClassName();
189                 if (FACES_SERVLET_CLASS.getName().equals(className)
190                         || isDelegatedFacesServlet(className))
191                 {
192                     // we found a FacesServlet; set an attribute for use during initialization
193                     servletContext.setAttribute(FACES_SERVLET_FOUND, Boolean.TRUE);                    
194                     return;
195                 }
196             }
197 
198             // the FacesServlet is not installed yet - install it
199             ServletRegistration.Dynamic servlet = servletContext.addServlet(FACES_SERVLET_NAME, FACES_SERVLET_CLASS);
200 
201             //try to add typical JSF mappings
202             String[] mappings = isAutomaticXhtmlMappingDisabled(servletContext) ? 
203                         FACES_SERVLET_MAPPINGS : FACES_SERVLET_FULL_MAPPINGS;
204             Set<String> conflictMappings = servlet.addMapping(mappings);
205             if (conflictMappings != null && !conflictMappings.isEmpty())
206             {
207                 //at least one of the attempted mappings is in use, remove and try again
208                 Set<String> newMappings = new HashSet<String>(Arrays.asList(mappings));
209                 newMappings.removeAll(conflictMappings);
210                 mappings = newMappings.toArray(new String[newMappings.size()]);
211                 servlet.addMapping(mappings);
212             }
213 
214             if (mappings != null && mappings.length > 0)
215             {
216                 // at least one mapping was added 
217                 // now we have to set a field in the ServletContext to indicate that we have
218                 // added the mapping dynamically, because MyFaces just parsed the web.xml to
219                 // find mappings and thus it would abort initializing
220                 servletContext.setAttribute(FACES_SERVLET_ADDED_ATTRIBUTE, Boolean.TRUE);
221 
222                 // add a log message
223                 log.log(Level.INFO, "Added FacesServlet with mappings="
224                         + Arrays.toString(mappings));
225             }
226         }
227     }
228 
229     /**
230      * Checks if the <code>INITIALIZE_ALWAYS_STANDALONE</code> flag is ture in <code>web.xml</code>.
231      * If the flag is true, this means we should not add the FacesServlet, instead we want to
232      * init MyFaces regardless...
233      */
234     private boolean shouldStartupRegardless(ServletContext servletContext)
235     {
236         try
237         {
238             String standaloneStartup = servletContext.getInitParameter(INITIALIZE_ALWAYS_STANDALONE);
239 
240             // "true".equalsIgnoreCase(param) is faster than Boolean.valueOf()
241             return "true".equalsIgnoreCase(standaloneStartup);
242         }
243         catch (Exception e)
244         {
245             return false;
246         }
247     }
248 
249     /**
250      * Checks if the <code>INITIALIZE_SCAN_JARS_FOR_FACES_CONFIG</code> flag is true in <code>web.xml</code>.
251      * If the flag is true, this means we should scan app jars for *.faces-config.xml before adding
252      * any FacesServlet; in false, we skip that scan for performance.
253      */
254     private boolean shouldSkipJarFacesConfigScan(ServletContext servletContext)
255     {
256         try
257         {
258             String skipJarScan = servletContext.getInitParameter(INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN);
259 
260             if (skipJarScan == null)
261             {
262                 skipJarScan = System.getProperty(INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN);
263             }
264             return "true".equalsIgnoreCase(skipJarScan);
265         }
266         catch (Exception e)
267         {
268             return false;
269         }
270     }
271     
272     private boolean isAutomaticXhtmlMappingDisabled(ServletContext servletContext)
273     {
274         try
275         {
276             String xhtmlMappingDisabled = servletContext.getInitParameter(
277                     FacesServlet.DISABLE_FACESSERVLET_TO_XHTML_PARAM_NAME);
278 
279             if (xhtmlMappingDisabled == null)
280             {
281                 xhtmlMappingDisabled = "false";
282             }
283             return "true".equalsIgnoreCase(xhtmlMappingDisabled);
284         }
285         catch (Exception e)
286         {
287             return false;
288         }
289     }
290 
291     /**
292      * Checks if /WEB-INF/faces-config.xml is present.
293      * @return
294      */
295     private boolean isFacesConfigPresent(ServletContext servletContext)
296     {
297         try
298         {
299             // 1. A faces-config.xml file is found in WEB-INF
300             if (servletContext.getResource(FACES_CONFIG_RESOURCE) != null)
301             {
302                 return true;
303             }
304 
305             // 4. The javax.faces.CONFIG_FILES context param is declared in web.xml or web-fragment.xml.
306             // check for alternate faces-config files specified by javax.faces.CONFIG_FILES
307             String configFilesAttrValue = servletContext.getInitParameter(FacesServlet.CONFIG_FILES_ATTR);
308             if (configFilesAttrValue != null)
309             {
310                 String[] configFiles = configFilesAttrValue.split(",");
311                 for (String file : configFiles)
312                 {
313                     if (servletContext.getResource(file.trim()) != null)
314                     {
315                         return true;
316                     }
317                 }
318             }
319 
320             // Skip this scan - for performance - if INITIALIZE_SKIP_JAR_FACES_CONFIG_SCAN is set to true 
321             // 2. A faces-config.xml file is found in the META-INF directory of a jar in the 
322             //    application's classpath.
323             // 3. A filename ending in .faces-config.xml is found in the META-INF directory of a jar in 
324             //    the application's classpath.
325             // To do this properly it is necessary to use some SPI interfaces MyFaces already has, to 
326             // deal with OSGi and other
327             // environments properly.
328             if (!shouldSkipJarFacesConfigScan(servletContext)) 
329             {
330                 ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, true);
331                 FacesConfigResourceProviderFactory factory = FacesConfigResourceProviderFactory.
332                     getFacesConfigResourceProviderFactory(externalContext);
333                 FacesConfigResourceProvider provider = factory.createFacesConfigResourceProvider(externalContext);
334                 Collection<URL> metaInfFacesConfigUrls =  provider.getMetaInfConfigurationResources(externalContext);
335                 
336                 if (metaInfFacesConfigUrls != null && !metaInfFacesConfigUrls.isEmpty())
337                 {
338                     return true;
339                 }
340             }
341             return false;
342         }
343         catch (Exception e)
344         {
345             return false;
346         }
347     }
348 
349     /**
350      * Checks if the class represented by className implements DelegatedFacesServlet.
351      * @param className
352      * @return
353      */
354     private boolean isDelegatedFacesServlet(String className)
355     {
356         if (className == null)
357         {
358             // The class name can be null if this is e.g., a JSP mapped to
359             // a servlet.
360 
361             return false;
362         }
363         try
364         {
365             Class<?> clazz = Class.forName(className);
366             return DELEGATED_FACES_SERVLET_CLASS.isAssignableFrom(clazz);
367         }
368         catch (ClassNotFoundException cnfe)
369         {
370             return false;
371         }
372     }
373 }