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