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