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.webapp;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.lang.reflect.Method;
23  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
24  import org.apache.myfaces.config.FacesConfigValidator;
25  import org.apache.myfaces.config.FacesConfigurator;
26  import org.apache.myfaces.config.ManagedBeanBuilder;
27  import org.apache.myfaces.config.RuntimeConfig;
28  import org.apache.myfaces.config.element.ManagedBean;
29  import org.apache.myfaces.context.ReleaseableExternalContext;
30  import org.apache.myfaces.context.servlet.StartupFacesContextImpl;
31  import org.apache.myfaces.context.servlet.StartupServletExternalContextImpl;
32  import org.apache.myfaces.shared.context.ExceptionHandlerImpl;
33  import org.apache.myfaces.shared.util.StateUtils;
34  import org.apache.myfaces.shared.util.WebConfigParamUtils;
35  import org.apache.myfaces.spi.WebConfigProvider;
36  import org.apache.myfaces.spi.WebConfigProviderFactory;
37  import org.apache.myfaces.view.facelets.tag.MetaRulesetImpl;
38  
39  import javax.el.ExpressionFactory;
40  import javax.faces.application.Application;
41  import javax.faces.application.ProjectStage;
42  import javax.faces.component.UIViewRoot;
43  import javax.faces.context.ExceptionHandler;
44  import javax.faces.context.ExternalContext;
45  import javax.faces.context.FacesContext;
46  import javax.faces.event.PostConstructApplicationEvent;
47  import javax.faces.event.PreDestroyApplicationEvent;
48  import javax.faces.event.SystemEvent;
49  import javax.servlet.ServletContext;
50  import java.util.ArrayList;
51  import java.util.List;
52  import java.util.Locale;
53  import java.util.Map;
54  import java.util.logging.Level;
55  import java.util.logging.Logger;
56  import org.apache.myfaces.util.ExternalSpecifications;
57  
58  /**
59   * Performs common initialization tasks.
60   */
61  public abstract class AbstractFacesInitializer implements FacesInitializer
62  {
63      /**
64       * The logger instance for this class.
65       */
66      //private static final Log log = LogFactory.getLog(AbstractFacesInitializer.class);
67      private static final Logger log = Logger.getLogger(AbstractFacesInitializer.class.getName());
68      
69      /**
70       * If the servlet mapping for the FacesServlet is added dynamically, Boolean.TRUE 
71       * is stored under this key in the ServletContext.
72       * ATTENTION: this constant is duplicate in MyFacesContainerInitializer.
73       */
74      private static final String FACES_SERVLET_ADDED_ATTRIBUTE = "org.apache.myfaces.DYNAMICALLY_ADDED_FACES_SERVLET";
75  
76      /**
77       * This parameter specifies the ExpressionFactory implementation to use.
78       */
79      @JSFWebConfigParam(since="1.2.7", group="EL")
80      protected static final String EXPRESSION_FACTORY = "org.apache.myfaces.EXPRESSION_FACTORY";
81      
82      /**
83       * If this param is set to true, the check for faces servlet mapping is not done 
84       */
85      @JSFWebConfigParam(since="2.0.3", defaultValue="false")
86      protected static final String INITIALIZE_ALWAYS_STANDALONE = "org.apache.myfaces.INITIALIZE_ALWAYS_STANDALONE";
87      
88      /**
89       * Indicate if log all web config params should be done before initialize the webapp. 
90       * <p>
91       * If is set in "auto" mode, web config params are only logged on "Development" and "Production" project stages.
92       * </p> 
93       */
94      @JSFWebConfigParam(expectedValues="true, auto, false", defaultValue="auto")
95      public static final String INIT_PARAM_LOG_WEB_CONTEXT_PARAMS = "org.apache.myfaces.LOG_WEB_CONTEXT_PARAMS";
96      public static final String INIT_PARAM_LOG_WEB_CONTEXT_PARAMS_DEFAULT ="auto";
97  
98      /**
99       * Performs all necessary initialization tasks like configuring this JSF
100      * application.
101      */
102     public void initFaces(ServletContext servletContext)
103     {
104         try
105         {
106             if (log.isLoggable(Level.FINEST))
107             {
108                 log.finest("Initializing MyFaces");
109             }
110 
111             // Some parts of the following configuration tasks have been implemented 
112             // by using an ExternalContext. However, that's no problem as long as no 
113             // one tries to call methods depending on either the ServletRequest or 
114             // the ServletResponse.
115             // JSF 2.0: FacesInitializer now has some new methods to
116             // use proper startup FacesContext and ExternalContext instances.
117             FacesContext facesContext = FacesContext.getCurrentInstance();
118             ExternalContext externalContext = facesContext.getExternalContext();
119 
120             // Parse and validate the web.xml configuration file
121             
122             if (!WebConfigParamUtils.getBooleanInitParameter(externalContext, INITIALIZE_ALWAYS_STANDALONE, false))
123             {
124                 WebConfigProvider webConfigProvider = WebConfigProviderFactory.getWebConfigProviderFactory(
125                         facesContext.getExternalContext()).getWebConfigProvider(facesContext.getExternalContext());
126 
127                 if (webConfigProvider.getFacesServletMappings(facesContext.getExternalContext()).isEmpty())
128                 {
129                     // check if the FacesServlet has been added dynamically
130                     // in a Servlet 3.0 environment by MyFacesContainerInitializer
131                     Boolean mappingAdded = (Boolean) servletContext.getAttribute(FACES_SERVLET_ADDED_ATTRIBUTE);
132                     if (mappingAdded == null || !mappingAdded)
133                     {
134                         if (log.isLoggable(Level.WARNING))
135                         {
136                             log.warning("No mappings of FacesServlet found. Abort initializing MyFaces.");
137                         }
138                         return;
139                     }
140                 }
141             }
142 
143             initContainerIntegration(servletContext, externalContext);
144 
145             String useEncryption = servletContext.getInitParameter(StateUtils.USE_ENCRYPTION);
146             if (!"false".equals(useEncryption)) // the default value is true
147             {
148                 StateUtils.initSecret(servletContext);
149             }
150 
151             // initialize eager managed beans
152             _createEagerBeans(facesContext);
153 
154             _dispatchApplicationEvent(servletContext, PostConstructApplicationEvent.class);
155 
156             if ( (facesContext.isProjectStage(ProjectStage.Development) || 
157                   facesContext.isProjectStage(ProjectStage.Production)) &&
158                  log.isLoggable(Level.INFO))
159             {
160                 log.info("ServletContext initialized.");
161             }
162 
163             WebConfigParamsLogger.logWebContextParams(facesContext);
164             
165             //Force output EL message
166             ExternalSpecifications.isUnifiedELAvailable();
167             ExternalSpecifications.isBeanValidationAvailable();
168 
169             // print out a very prominent log message if the project stage is != Production
170             if (!facesContext.isProjectStage(ProjectStage.Production) &&
171                 !facesContext.isProjectStage(ProjectStage.UnitTest))
172             {
173                 ProjectStage projectStage = facesContext.getApplication().getProjectStage();
174                 StringBuilder message = new StringBuilder("\n\n");
175                 message.append("*******************************************************************\n");
176                 message.append("*** WARNING: Apache MyFaces-2 is running in ");
177                 message.append(projectStage.name().toUpperCase());        
178                 message.append(" mode.");
179                 int length = projectStage.name().length();
180                 for (int i = 0; i < 11 - length; i++)
181                 {
182                     message.append(" ");
183                 }
184                 message.append("   ***\n");
185                 message.append("***                                         ");
186                 for (int i = 0; i < length; i++)
187                 {
188                     message.append("^");
189                 }
190                 for (int i = 0; i < 20 - length; i++)
191                 {
192                     message.append(" ");
193                 }
194                 message.append("***\n");
195                 message.append("*** Do NOT deploy to your live server(s) without changing this. ***\n");
196                 message.append("*** See Application#getProjectStage() for more information.     ***\n");
197                 message.append("*******************************************************************\n");
198                 log.log(Level.WARNING, message.toString());
199             }
200 
201         }
202         catch (Exception ex)
203         {
204             log.log(Level.SEVERE, "An error occured while initializing MyFaces: "
205                       + ex.getMessage(), ex);
206         }
207     }
208     
209     /**
210      * Checks for application scoped managed-beans with eager=true,
211      * creates them and stores them in the application map.
212      * @param facesContext
213      */
214     private void _createEagerBeans(FacesContext facesContext)
215     {
216         ExternalContext externalContext = facesContext.getExternalContext();
217         RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
218         List<ManagedBean> eagerBeans = new ArrayList<ManagedBean>();
219         
220         // check all registered managed-beans
221         for (ManagedBean bean : runtimeConfig.getManagedBeans().values())
222         {
223             String eager = bean.getEager();
224             if (eager != null && "true".equals(eager))
225             {
226                 // eager beans are only allowed for application scope
227                 if (ManagedBeanBuilder.APPLICATION.equals(bean.getManagedBeanScope()))
228                 {
229                     // add to eager beans
230                     eagerBeans.add(bean);
231                 }
232                 else
233                 {
234                     // log warning and continue (the bean will be lazy loaded)
235                     log.log(Level.WARNING, "The managed-bean with name "
236                             + bean.getManagedBeanName()
237                             + " must be application scoped to support eager=true.");
238                 }
239             }
240         }
241         
242         // check if there are any eager beans
243         if (!eagerBeans.isEmpty())
244         {
245             ManagedBeanBuilder managedBeanBuilder = new ManagedBeanBuilder();
246             Map<String, Object> applicationMap = externalContext.getApplicationMap();
247             
248             for (ManagedBean bean : eagerBeans)
249             {
250                 // check application scope for bean instance
251                 if (applicationMap.containsKey(bean.getManagedBeanName()))
252                 {
253                     // do not build bean, because it already exists
254                     // (e.g. @ManagedProperty from previous managed bean already created it)
255                     continue;
256                 }
257 
258                 // create instance
259                 Object beanInstance = managedBeanBuilder.buildManagedBean(facesContext, bean);
260                 
261                 // put in application scope
262                 applicationMap.put(bean.getManagedBeanName(), beanInstance);
263             }
264         }
265     }
266 
267     /**
268      * Eventually we can use our plugin infrastructure for this as well
269      * it would be a cleaner interception point than the base class
270      * but for now this position is valid as well
271      * <p/>
272      * Note we add it for now here because the application factory object
273      * leaves no possibility to have a destroy interceptor
274      * and applications are per web application singletons
275      * Note if this does not work out
276      * move the event handler into the application factory
277      *
278      * @param servletContext the servlet context to be passed down
279      * @param eventClass     the class to be passed down into the dispatching
280      *                       code
281      */
282     private void _dispatchApplicationEvent(ServletContext servletContext, Class<? extends SystemEvent> eventClass)
283     {
284         FacesContext facesContext = FacesContext.getCurrentInstance();
285         Application application = facesContext.getApplication();
286         application.publishEvent(facesContext, eventClass, Application.class, application);
287     }
288     
289     /**
290      * Cleans up all remaining resources (well, theoretically).
291      */
292     public void destroyFaces(ServletContext servletContext)
293     {
294 
295         FacesContext facesContext = FacesContext.getCurrentInstance();
296 
297         if (!WebConfigParamUtils.getBooleanInitParameter(facesContext.getExternalContext(),
298                                                          INITIALIZE_ALWAYS_STANDALONE, false))
299         {
300             //We need to check if the current application was initialized by myfaces
301             WebConfigProvider webConfigProvider = WebConfigProviderFactory.getWebConfigProviderFactory(
302                     facesContext.getExternalContext()).getWebConfigProvider(facesContext.getExternalContext());
303 
304             if (webConfigProvider.getFacesServletMappings(facesContext.getExternalContext()).isEmpty())
305             {
306                 // check if the FacesServlet has been added dynamically
307                 // in a Servlet 3.0 environment by MyFacesContainerInitializer
308                 Boolean mappingAdded = (Boolean) servletContext.getAttribute(FACES_SERVLET_ADDED_ATTRIBUTE);
309                 if (mappingAdded == null || !mappingAdded)
310                 {
311                     if (log.isLoggable(Level.WARNING))
312                     {
313                         log.warning("No mappings of FacesServlet found. Abort destroy MyFaces.");
314                     }
315                     return;
316                 }
317             }
318         }
319 
320         _dispatchApplicationEvent(servletContext, PreDestroyApplicationEvent.class);
321 
322         // clear the cache of MetaRulesetImpl in order to prevent a memory leak
323         MetaRulesetImpl.clearMetadataTargetCache();
324         
325         // clear UIViewParameter default renderer map
326         try
327         {
328             Class<?> c = Class.forName("javax.faces.component.UIViewParameter");
329             Method m = c.getDeclaredMethod("releaseRenderer");
330             m.setAccessible(true);
331             m.invoke(null);
332         }
333         catch(ClassNotFoundException e)
334         {
335             log.log(Level.SEVERE, e.getMessage(), e);
336         }
337         catch(NoSuchMethodException e)
338         {
339             log.log(Level.SEVERE, e.getMessage(), e);
340         }
341         catch(IllegalAccessException e)
342         {
343             log.log(Level.SEVERE, e.getMessage(), e);
344         }
345         catch(InvocationTargetException e)
346         {
347             log.log(Level.SEVERE, e.getMessage(), e);
348         }
349 
350         // TODO is it possible to make a real cleanup?
351     }
352 
353     /**
354      * Configures this JSF application. It's required that every
355      * FacesInitializer (i.e. every subclass) calls this method during
356      * initialization.
357      *
358      * @param servletContext    the current ServletContext
359      * @param externalContext   the current ExternalContext
360      * @param expressionFactory the ExpressionFactory to use
361      * @return the current runtime configuration
362      */
363     protected RuntimeConfig buildConfiguration(ServletContext servletContext,
364                                                ExternalContext externalContext, ExpressionFactory expressionFactory)
365     {
366         RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
367         runtimeConfig.setExpressionFactory(expressionFactory);
368 
369         // And configure everything
370         new FacesConfigurator(externalContext).configure();
371 
372         validateFacesConfig(servletContext, externalContext);
373 
374         return runtimeConfig;
375     }
376 
377     protected void validateFacesConfig(ServletContext servletContext, ExternalContext externalContext)
378     {
379         String validate = servletContext.getInitParameter(FacesConfigValidator.VALIDATE_CONTEXT_PARAM);
380         if ("true".equals(validate) && log.isLoggable(Level.WARNING))
381         { // the default value is false
382             List<String> warnings = FacesConfigValidator.validate(
383                     externalContext);
384 
385             for (String warning : warnings)
386             {
387                 log.warning(warning);
388             }
389         }
390     }
391 
392     /**
393      * Try to load user-definied ExpressionFactory. Returns <code>null</code>,
394      * if no custom ExpressionFactory was specified.
395      *
396      * @param externalContext the current ExternalContext
397      * @return User-specified ExpressionFactory, or
398      *         <code>null</code>, if no no custom implementation was specified
399      */
400     protected static ExpressionFactory getUserDefinedExpressionFactory(ExternalContext externalContext)
401     {
402         String expressionFactoryClassName
403                 = WebConfigParamUtils.getStringInitParameter(externalContext, EXPRESSION_FACTORY);
404         if (expressionFactoryClassName != null
405                 && expressionFactoryClassName.trim().length() > 0)
406         {
407             if (log.isLoggable(Level.FINE))
408             {
409                 log.fine("Attempting to load the ExpressionFactory implementation "
410                         + "you've specified: '" + expressionFactoryClassName + "'.");
411             }
412 
413             return loadExpressionFactory(expressionFactoryClassName);
414         }
415 
416         return null;
417     }
418 
419     /**
420      * Loads and instantiates the given ExpressionFactory implementation.
421      *
422      * @param expressionFactoryClassName the class name of the ExpressionFactory implementation
423      * @return the newly created ExpressionFactory implementation, or
424      *         <code>null</code>, if an error occurred
425      */
426     protected static ExpressionFactory loadExpressionFactory(String expressionFactoryClassName)
427     {
428         try
429         {
430             Class<?> expressionFactoryClass = Class.forName(expressionFactoryClassName);
431             return (ExpressionFactory) expressionFactoryClass.newInstance();
432         }
433         catch (Exception ex)
434         {
435             if (log.isLoggable(Level.FINE))
436             {
437                 log.log(Level.FINE, "An error occured while instantiating a new ExpressionFactory. "
438                         + "Attempted to load class '" + expressionFactoryClassName + "'.", ex);
439             }
440         }
441 
442         return null;
443     }
444 
445     public FacesContext initStartupFacesContext(ServletContext servletContext)
446     {
447         // We cannot use FacesContextFactory, because it is necessary to initialize 
448         // before Application and RenderKit factories, so we should use different object. 
449         return _createFacesContext(servletContext, true);
450     }
451         
452     public void destroyStartupFacesContext(FacesContext facesContext)
453     {
454         _releaseFacesContext(facesContext);
455     }
456     
457     public FacesContext initShutdownFacesContext(ServletContext servletContext)
458     {
459         return _createFacesContext(servletContext, false);
460     }
461         
462     public void destroyShutdownFacesContext(FacesContext facesContext)
463     {
464         _releaseFacesContext(facesContext);
465     }
466     
467     private FacesContext _createFacesContext(ServletContext servletContext, boolean startup)
468     {
469         ExternalContext externalContext = new StartupServletExternalContextImpl(servletContext, startup);
470         ExceptionHandler exceptionHandler = new ExceptionHandlerImpl();
471         FacesContext facesContext = new StartupFacesContextImpl(externalContext, 
472                 (ReleaseableExternalContext) externalContext, exceptionHandler, startup);
473         
474         // If getViewRoot() is called during application startup or shutdown, 
475         // it should return a new UIViewRoot with its locale set to Locale.getDefault().
476         UIViewRoot startupViewRoot = new UIViewRoot();
477         startupViewRoot.setLocale(Locale.getDefault());
478         facesContext.setViewRoot(startupViewRoot);
479         
480         return facesContext;
481     }
482     
483     private void _releaseFacesContext(FacesContext facesContext)
484     {        
485         // make sure that the facesContext gets released.
486         // This is important in an OSGi environment 
487         if (facesContext != null)
488         {
489             facesContext.release();
490         }        
491     }
492     
493     /**
494      * Performs initialization tasks depending on the current environment.
495      *
496      * @param servletContext  the current ServletContext
497      * @param externalContext the current ExternalContext
498      */
499     protected abstract void initContainerIntegration(
500             ServletContext servletContext, ExternalContext externalContext);
501 
502 }