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.mc.test.core;
20  
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.lang.reflect.Constructor;
25  import java.lang.reflect.Field;
26  import java.lang.reflect.Method;
27  import java.net.URI;
28  import java.net.URL;
29  import java.net.URLClassLoader;
30  import java.util.List;
31  import java.util.logging.Level;
32  import java.util.logging.Logger;
33  
34  import javax.el.ExpressionFactory;
35  import javax.faces.FacesException;
36  import javax.faces.FactoryFinder;
37  import javax.faces.application.Application;
38  import javax.faces.application.FacesMessage;
39  import javax.faces.application.ProjectStage;
40  import javax.faces.application.ViewHandler;
41  import javax.faces.component.UIViewRoot;
42  import javax.faces.context.ExternalContext;
43  import javax.faces.context.FacesContext;
44  import javax.faces.context.FacesContextFactory;
45  import javax.faces.context.Flash;
46  import javax.faces.event.ExceptionQueuedEvent;
47  import javax.faces.event.ExceptionQueuedEventContext;
48  import javax.faces.event.PhaseId;
49  import javax.faces.event.PhaseListener;
50  import javax.faces.event.PreRenderViewEvent;
51  import javax.faces.lifecycle.Lifecycle;
52  import javax.faces.lifecycle.LifecycleFactory;
53  import javax.faces.view.ViewDeclarationLanguage;
54  import javax.faces.webapp.FacesServlet;
55  import javax.servlet.ServletContext;
56  import javax.servlet.ServletContextEvent;
57  import javax.servlet.http.HttpServletResponse;
58  
59  import org.apache.myfaces.config.ConfigFilesXmlValidationUtils;
60  import org.apache.myfaces.config.DefaultFacesConfigurationProvider;
61  import org.apache.myfaces.config.RuntimeConfig;
62  import org.apache.myfaces.config.element.FacesConfig;
63  import org.apache.myfaces.config.impl.digester.elements.Factory;
64  import org.apache.myfaces.lifecycle.LifecycleImpl;
65  import org.apache.myfaces.lifecycle.ViewNotFoundException;
66  import org.apache.myfaces.mc.test.core.annotation.DeclareFacesConfig;
67  import org.apache.myfaces.mc.test.core.annotation.ManagedBeans;
68  import org.apache.myfaces.mc.test.core.annotation.PageBean;
69  import org.apache.myfaces.shared.config.MyfacesConfig;
70  import org.apache.myfaces.spi.FacesConfigurationProvider;
71  import org.apache.myfaces.spi.impl.DefaultFacesConfigurationProviderFactory;
72  import org.apache.myfaces.test.el.MockExpressionFactory;
73  import org.apache.myfaces.test.mock.MockPrintWriter;
74  import org.apache.myfaces.test.mock.MockServletConfig;
75  import org.apache.myfaces.test.mock.MockServletContext;
76  import org.apache.myfaces.util.DebugUtils;
77  import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
78  import org.apache.myfaces.webapp.AbstractFacesInitializer;
79  import org.apache.myfaces.webapp.StartupServletContextListener;
80  import org.junit.After;
81  import org.junit.Before;
82  import org.xml.sax.SAXException;
83  
84  /**
85   * <p>Abstract JUnit test case base class, which sets up MyFaces Core environment
86   * using mock object for the outer servlet environment.</p>
87   * <p>Since jsp engine is not bundled with MyFaces, this configuration is able to 
88   * handle facelet pages only.</p>
89   * 
90   * @author Leonardo Uribe
91   *
92   */
93  public abstract class AbstractMyFacesTestCase
94  {
95      private static Class<?> PHASE_EXECUTOR_CLASS = null;
96      private static Class<?> PHASE_MANAGER_CLASS = null;
97      
98      static {
99          try
100         {
101             PHASE_EXECUTOR_CLASS = Class.forName("org.apache.myfaces.lifecycle.PhaseExecutor");
102             PHASE_MANAGER_CLASS = Class.forName("org.apache.myfaces.lifecycle.PhaseListenerManager");
103         }
104         catch (ClassNotFoundException e)
105         {
106             //No op
107         }
108     }
109     
110     public static final String PHASE_MANAGER_INSTANCE = "org.apache.myfaces.test.PHASE_MANAGER_INSTANCE";
111     
112     public static final String LAST_PHASE_PROCESSED = "oam.LAST_PHASE_PROCESSED";
113     
114     public static final String LAST_RENDER_PHASE_STEP = "oam.LAST_RENDER_PHASE_STEP";
115     
116     public static final int BEFORE_RENDER_STEP = 1;
117     public static final int BUILD_VIEW_CYCLE_STEP = 2;
118     public static final int VIEWHANDLER_RENDER_STEP = 3;
119     public static final int AFTER_RENDER_STEP = 4;
120     
121     // ------------------------------------------------------------ Constructors
122 
123     /**
124      * <p>Construct a new instance of this test case.</p>
125      *
126      * @param name Name of this test case
127      */    
128     public AbstractMyFacesTestCase()
129     {
130     }
131 
132     // ---------------------------------------------------- Overall Test Methods
133 
134     /**
135      * <p>Set up instance variables required by this test case.</p>
136      */
137     @Before
138     public void setUp() throws Exception
139     {
140         // Set up a new thread context class loader
141         threadContextClassLoader = Thread.currentThread()
142                 .getContextClassLoader();
143         Thread.currentThread()
144                 .setContextClassLoader(
145                         new URLClassLoader(new URL[0], this.getClass()
146                                 .getClassLoader()));
147 
148         // Set up Servlet API Objects
149         setUpServletObjects();
150 
151         // Set up JSF API Objects
152         FactoryFinder.releaseFactories();
153 
154         setUpServletListeners();
155         
156         setUpFacesServlet();
157     }
158     
159     /**
160      * <p>Setup servlet objects that will be used for the test:</p>
161      * 
162      * <ul>
163      * <li><code>servletConfig</code> (<code>MockServletConfig</code>)</li>
164      * <li><code>servletContext</code> (<code>MockServletContext</code>)</li>
165      * </ul>
166      * 
167      * @throws Exception
168      */
169     protected void setUpServletObjects() throws Exception
170     {
171         servletContext = new MockServletContext();
172         servletConfig = new MockServletConfig(servletContext);
173         servletContext.setDocumentRoot(getWebappContextURI());
174         setUpWebConfigParams();
175     }
176     
177     /**
178      * <p>Setup web config params. By default it sets the following params</p>
179      * 
180      * <ul>
181      * <li>"org.apache.myfaces.INITIALIZE_ALWAYS_STANDALONE", "true"</li>
182      * <li>"javax.faces.PROJECT_STAGE", "UnitTest"</li>
183      * <li>"javax.faces.PARTIAL_STATE_SAVING", "true"</li>
184      * <li>"javax.faces.FACELETS_REFRESH_PERIOD", "-1"</li>
185      * </ul>
186      * 
187      * @throws Exception
188      */
189     protected void setUpWebConfigParams() throws Exception
190     {
191         servletContext.addInitParameter("org.apache.myfaces.INITIALIZE_ALWAYS_STANDALONE", "true");
192         servletContext.addInitParameter("javax.faces.PROJECT_STAGE", "UnitTest");
193         servletContext.addInitParameter("javax.faces.PARTIAL_STATE_SAVING", "true");
194         servletContext.addInitParameter(FaceletViewDeclarationLanguage.PARAM_REFRESH_PERIOD,"-1");
195     }
196     
197     /**
198      * <p>Return an URI that identifies the base path that will be used by servletContext
199      * to load resources like facelet files an others. By default it points to the directory
200      * path calculated from the package name of the child test class.</p>
201      * 
202      * @return
203      */
204     protected URI getWebappContextURI()
205     {
206         try
207         {
208             ClassLoader cl = Thread.currentThread().getContextClassLoader();
209             URL url = cl.getResource(getWebappContextFilePath());
210             if (url == null)
211             {
212                 throw new FileNotFoundException(cl.getResource("").getFile()
213                         + getWebappContextFilePath() + " was not found");
214             }
215             else
216             {
217                 return new URI(url.toString());
218             }
219         }
220         catch (Exception e)
221         {
222             throw new RuntimeException("Error Initializing Context", e);
223         }
224     }
225     
226     /**
227      * Return a path that is used to load resources like facelet files an others.
228      * By default it points to the directory path calculated from the package 
229      * name of the child test class.
230      * 
231      * @return
232      */
233     protected String getWebappContextFilePath()
234     {
235         return this.getClass().getName().substring(0,
236                 this.getClass().getName().lastIndexOf('.')).replace('.', '/')
237                 + "/";
238     }
239     
240     /**
241      * Create the ExpressionFactory instance that will be used to initialize the test
242      * environment. By default it uses MockExpressionFactory. 
243      * 
244      * @return
245      */
246     protected ExpressionFactory createExpressionFactory()
247     {
248         return new MockExpressionFactory();
249     }
250     
251     /**
252      * setup servlets avaliable in the test environment
253      * 
254      * @throws Exception
255      */
256     protected void setUpServlets() throws Exception
257     {
258         setUpFacesServlet();
259     }
260     
261     /**
262      * setup listeners avaliable in the test environment
263      * 
264      * @throws Exception
265      */
266     protected void setUpServletListeners() throws Exception
267     {
268         setUpMyFaces();
269     }
270     
271     /**
272      * 
273      * @return
274      */
275     protected FacesConfigurationProvider createFacesConfigurationProvider()
276     {
277         return new MyFacesMockFacesConfigurationProvider(this); 
278     }
279     
280     protected void setUpMyFaces() throws Exception
281     {
282         if (facesConfigurationProvider == null)
283         {
284             facesConfigurationProvider = createFacesConfigurationProvider();
285         }
286         servletContext.setAttribute(
287                 DefaultFacesConfigurationProviderFactory.FACES_CONFIGURATION_PROVIDER_INSTANCE_KEY, 
288                 facesConfigurationProvider);
289         listener = new StartupServletContextListener();
290         listener.setFacesInitializer(new AbstractFacesInitializer()
291         {
292             
293             @Override
294             protected void initContainerIntegration(ServletContext servletContext,
295                     ExternalContext externalContext)
296             {
297                 ExpressionFactory expressionFactory = createExpressionFactory();
298 
299                 RuntimeConfig runtimeConfig = buildConfiguration(servletContext, externalContext, expressionFactory);
300             }
301         });
302         listener.contextInitialized(new ServletContextEvent(servletContext));
303     }
304 
305     protected void tearDownMyFaces() throws Exception
306     {
307         //Don't tear down FacesConfigurationProvider, because that is shared by all tests.
308         //This helps to reduce the time each test takes 
309         //facesConfigurationProvider = null
310         
311         listener.contextDestroyed(new ServletContextEvent(servletContext));
312     }
313 
314     protected void setUpFacesServlet() throws Exception
315     {
316         lifecycleFactory = (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
317         facesContextFactory = (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
318         lifecycle = lifecycleFactory.getLifecycle(getLifecycleId());
319     }
320     
321     protected void tearDownFacesServlet() throws Exception
322     {
323         lifecycleFactory = null;
324         facesContextFactory = null;
325     }
326     
327     protected void tearDownServlets() throws Exception
328     {
329         tearDownFacesServlet();
330     }
331     
332     protected void tearDownServletListeners() throws Exception
333     {
334         tearDownMyFaces();
335     }
336 
337     @After
338     public void tearDown() throws Exception
339     {
340         tearDownServlets();
341 
342         tearDownServletListeners();
343         
344         listener = null;
345         
346         servletConfig = null;
347         servletContext = null;
348         
349         FactoryFinder.releaseFactories();
350         
351         Thread.currentThread().setContextClassLoader(threadContextClassLoader);
352         threadContextClassLoader = null;
353     }
354     
355     private String getLifecycleId()
356     {
357         // 1. check for Servlet's init-param
358         // 2. check for global context parameter
359         // 3. use default Lifecycle Id, if none of them was provided
360         String serLifecycleId = servletConfig.getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
361         String appLifecycleId = servletConfig.getServletContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
362         appLifecycleId = serLifecycleId == null ? appLifecycleId : serLifecycleId;
363         return appLifecycleId != null ? appLifecycleId : LifecycleFactory.DEFAULT_LIFECYCLE;
364     }
365 
366     /**
367      * Call lifecycle.execute(facesContext)
368      * 
369      * @param facesContext
370      */
371     protected void processLifecycleExecute(FacesContext facesContext)
372     {
373         lifecycle.execute(facesContext);
374         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.INVOKE_APPLICATION);
375     }
376 
377     /**
378      * Execute restore view phase.
379      * 
380      * @param facesContext
381      * @throws Exception
382      */
383     protected void processRestoreViewPhase(FacesContext facesContext) throws Exception
384     {
385         executePhase(facesContext, PhaseId.RESTORE_VIEW);
386         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.RESTORE_VIEW);
387     }
388     
389     /**
390      * Execute apply request values phase. If the responseComplete or renderResponse
391      * flags are set, it returns without do any action.
392      * 
393      * @param facesContext
394      * @throws Exception
395      */
396     protected void processApplyRequestValuesPhase(FacesContext facesContext) throws Exception
397     {
398         if (facesContext.getRenderResponse() || facesContext.getResponseComplete())
399         {
400             return;
401         }
402         executePhase(facesContext, PhaseId.APPLY_REQUEST_VALUES);
403         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.APPLY_REQUEST_VALUES);
404     }
405 
406     /**
407      * Execute process validations phase. If the responseComplete or renderResponse
408      * flags are set, it returns without do any action.
409      * 
410      * @param facesContext
411      * @throws Exception
412      */
413     protected void processValidationsPhase(FacesContext facesContext) throws Exception
414     {
415         if (facesContext.getRenderResponse() || facesContext.getResponseComplete())
416         {
417             return;
418         }
419         executePhase(facesContext, PhaseId.PROCESS_VALIDATIONS);
420         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.PROCESS_VALIDATIONS);
421     }
422 
423     /**
424      * Execute update model phase. If the responseComplete or renderResponse
425      * flags are set, it returns without do any action.
426      * 
427      * @param facesContext
428      * @throws Exception
429      */
430     protected void processUpdateModelPhase(FacesContext facesContext) throws Exception
431     {
432         if (facesContext.getRenderResponse() || facesContext.getResponseComplete())
433         {
434             return;
435         }
436         executePhase(facesContext, PhaseId.UPDATE_MODEL_VALUES);
437         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.UPDATE_MODEL_VALUES);
438 
439     }
440     
441     /**
442      * Execute invoke application phase. If the responseComplete or renderResponse
443      * flags are set, it returns without do any action.
444      * 
445      * @param facesContext
446      * @throws Exception
447      */
448     protected void processInvokeApplicationPhase(FacesContext facesContext) throws Exception
449     {
450         if (facesContext.getRenderResponse() || facesContext.getResponseComplete())
451         {
452             return;
453         }
454         executePhase(facesContext, PhaseId.INVOKE_APPLICATION);
455         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.INVOKE_APPLICATION);
456     }
457 
458     /**
459      * Call lifecycle.render(facesContext)
460      * 
461      * @param facesContext
462      */
463     protected void processRender(FacesContext facesContext) throws Exception
464     {
465         processRemainingExecutePhases(facesContext);
466         lifecycle.render(facesContext);
467         facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.RENDER_RESPONSE);
468         facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, AFTER_RENDER_STEP);
469     }
470     
471     protected void processRemainingExecutePhases(FacesContext facesContext) throws Exception
472     {
473         PhaseId lastPhaseId = (PhaseId) facesContext.getAttributes().get(LAST_PHASE_PROCESSED);
474         if (lastPhaseId == null)
475         {
476             processLifecycleExecute(facesContext);
477             return;
478         }
479         else
480         {
481             boolean continueProcess = false;
482             if (PhaseId.RESTORE_VIEW.equals(lastPhaseId))
483             {
484                 processApplyRequestValuesPhase(facesContext);
485                 continueProcess = true;
486             }
487             if (continueProcess || PhaseId.APPLY_REQUEST_VALUES.equals(lastPhaseId))
488             {
489                 processValidationsPhase(facesContext);
490                 continueProcess = true;
491             }
492             if (continueProcess || PhaseId.PROCESS_VALIDATIONS.equals(lastPhaseId))
493             {
494                 processUpdateModelPhase(facesContext);
495                 continueProcess = true;
496             }
497             if (continueProcess || PhaseId.UPDATE_MODEL_VALUES.equals(lastPhaseId))
498             {
499                 processInvokeApplicationPhase(facesContext);
500                 continueProcess = true;
501             }
502         }
503     }
504 
505     protected void processRemainingPhases(FacesContext facesContext) throws Exception
506     {
507         PhaseId lastPhaseId = (PhaseId) facesContext.getAttributes().get(LAST_PHASE_PROCESSED);
508         if (lastPhaseId == null)
509         {
510             processLifecycleExecute(facesContext);
511             processRender(facesContext);
512             return;
513         }
514         else
515         {
516             boolean continueProcess = false;
517             if (PhaseId.RESTORE_VIEW.equals(lastPhaseId))
518             {
519                 processApplyRequestValuesPhase(facesContext);
520                 continueProcess = true;
521             }
522             if (continueProcess || PhaseId.APPLY_REQUEST_VALUES.equals(lastPhaseId))
523             {
524                 processValidationsPhase(facesContext);
525                 continueProcess = true;
526             }
527             if (continueProcess || PhaseId.PROCESS_VALIDATIONS.equals(lastPhaseId))
528             {
529                 processUpdateModelPhase(facesContext);
530                 continueProcess = true;
531             }
532             if (continueProcess || PhaseId.UPDATE_MODEL_VALUES.equals(lastPhaseId))
533             {
534                 processInvokeApplicationPhase(facesContext);
535                 continueProcess = true;
536             }
537             if (continueProcess || PhaseId.INVOKE_APPLICATION.equals(lastPhaseId))
538             {
539                 Integer step = (Integer) facesContext.getAttributes().get(LAST_RENDER_PHASE_STEP);
540                 if (step == null)
541                 {
542                     processRender(facesContext);
543                 }
544                 else
545                 {
546                     if (BEFORE_RENDER_STEP == step.intValue())
547                     {
548                         executeBuildViewCycle(facesContext);
549                         executeViewHandlerRender(facesContext);
550                         executeAfterRender(facesContext);
551                     }
552                     else if (BUILD_VIEW_CYCLE_STEP == step.intValue())
553                     {
554                         executeViewHandlerRender(facesContext);
555                         executeAfterRender(facesContext);
556                     }
557                     else if (VIEWHANDLER_RENDER_STEP == step.intValue())
558                     {
559                         executeAfterRender(facesContext);
560                     }
561                 }
562             }
563         }
564     }
565     
566     /**
567      * Indicate if annotation scanning should be done over the classpath. 
568      * By default it is set to false.
569      * 
570      * @return
571      */
572     protected boolean isScanAnnotations()
573     {
574         return false;
575     }
576     
577     protected void executeBeforeRender(FacesContext facesContext) throws Exception
578     {
579         if (lifecycle instanceof LifecycleImpl)
580         {
581             LifecycleImpl lifecycleImpl = (LifecycleImpl) lifecycle;
582             
583             Object phaseExecutor = null;
584             Field renderExecutorField = lifecycleImpl.getClass().getDeclaredField("renderExecutor");
585             if (!renderExecutorField.isAccessible())
586             {
587                 renderExecutorField.setAccessible(true);
588             }
589             phaseExecutor = renderExecutorField.get(lifecycleImpl);
590             
591             if (facesContext.getResponseComplete())
592             {
593                 return;
594             }
595             
596             Object phaseManager = facesContext.getAttributes().get(PHASE_MANAGER_INSTANCE);
597             if (phaseManager == null)
598             {
599                 Method getPhaseListenersMethod = lifecycleImpl.getClass().getDeclaredMethod("getPhaseListeners");
600                 if (!getPhaseListenersMethod.isAccessible())
601                 {
602                     getPhaseListenersMethod.setAccessible(true);
603                 }
604                 
605                 Constructor<?> plmc = PHASE_MANAGER_CLASS.getDeclaredConstructor(new Class[]{Lifecycle.class, FacesContext.class, PhaseListener[].class});
606                 if (!plmc.isAccessible())
607                 {
608                     plmc.setAccessible(true);
609                 }
610                 phaseManager = plmc.newInstance(lifecycle, facesContext, getPhaseListenersMethod.invoke(lifecycleImpl, null));
611                 facesContext.getAttributes().put(PHASE_MANAGER_INSTANCE, phaseManager);
612             }
613             
614             Flash flash = facesContext.getExternalContext().getFlash();
615             
616             try
617             {
618                 facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
619                 
620                 flash.doPrePhaseActions(facesContext);
621                 
622                 // let the PhaseExecutor do some pre-phase actions
623                 
624                 //renderExecutor.doPrePhaseActions(facesContext);
625                 Method doPrePhaseActionsMethod = phaseExecutor.getClass().getMethod("doPrePhaseActions", FacesContext.class);
626                 if(!(doPrePhaseActionsMethod.isAccessible()))
627                 {
628                     doPrePhaseActionsMethod.setAccessible(true);
629                 }
630                 doPrePhaseActionsMethod.invoke(phaseExecutor, facesContext);
631                 
632                 //phaseListenerMgr.informPhaseListenersBefore(PhaseId.RENDER_RESPONSE);
633                 Method informPhaseListenersBeforeMethod = phaseManager.getClass().getDeclaredMethod("informPhaseListenersBefore", PhaseId.class);
634                 if(!(informPhaseListenersBeforeMethod.isAccessible()))
635                 {
636                     informPhaseListenersBeforeMethod.setAccessible(true);
637                 }
638                 informPhaseListenersBeforeMethod.invoke(phaseManager, PhaseId.RENDER_RESPONSE);
639                 
640                 // also possible that one of the listeners completed the response
641                 if (facesContext.getResponseComplete())
642                 {
643                     return;
644                 }
645                 
646                 //renderExecutor.execute(facesContext);
647             }
648             
649             catch (Throwable e)
650             {
651                 // JSF 2.0: publish the executor's exception (if any).
652                 ExceptionQueuedEventContext context = new ExceptionQueuedEventContext (facesContext, e, null, PhaseId.RENDER_RESPONSE);
653                 facesContext.getApplication().publishEvent (facesContext, ExceptionQueuedEvent.class, context);
654             }
655             
656             finally
657             {
658                 /*
659                 phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase());
660                 flash.doPostPhaseActions(facesContext);
661                 
662                 // publish a field in the application map to indicate
663                 // that the first request has been processed
664                 requestProcessed(facesContext);
665                 */
666             }
667             
668             facesContext.getExceptionHandler().handle();
669             
670 
671             facesContext.getAttributes().remove(PHASE_MANAGER_INSTANCE);
672             
673             facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, BEFORE_RENDER_STEP);
674         }
675         else
676         {
677             throw new UnsupportedOperationException("Cannot execute phase on custom lifecycle instances");
678         }
679     }
680     
681     public void executeBuildViewCycle(FacesContext facesContext) throws Exception
682     {
683         Application application = facesContext.getApplication();
684         ViewHandler viewHandler = application.getViewHandler();
685         UIViewRoot root;
686         UIViewRoot previousRoot;
687         String viewId;
688         String newViewId;
689         boolean isNotSameRoot;
690         int loops = 0;
691         int maxLoops = 15;
692         
693         if (facesContext.getViewRoot() == null)
694         {
695             throw new ViewNotFoundException("A view is required to execute "+facesContext.getCurrentPhaseId());
696         }
697         
698         try
699         {
700             // do-while, because the view might change in PreRenderViewEvent-listeners
701             do
702             {
703                 root = facesContext.getViewRoot();
704                 previousRoot = root;
705                 viewId = root.getViewId();
706                 
707                 ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(
708                         facesContext, viewId);
709                 if (vdl != null)
710                 {
711                     vdl.buildView(facesContext, root);
712                 }
713                 
714                 // publish a PreRenderViewEvent: note that the event listeners
715                 // of this event can change the view, so we have to perform the algorithm 
716                 // until the viewId does not change when publishing this event.
717                 application.publishEvent(facesContext, PreRenderViewEvent.class, root);
718                 
719                 // was the response marked as complete by an event listener?
720                 if (facesContext.getResponseComplete())
721                 {
722                     return;
723                 }
724 
725                 root = facesContext.getViewRoot();
726                 
727                 newViewId = root.getViewId();
728                 
729                 isNotSameRoot = !( (newViewId == null ? newViewId == viewId : newViewId.equals(viewId) ) && 
730                         previousRoot.equals(root) ); 
731                 
732                 loops++;
733             }
734             while ((newViewId == null && viewId != null) 
735                     || (newViewId != null && (!newViewId.equals(viewId) || isNotSameRoot ) ) && loops < maxLoops);
736             
737             if (loops == maxLoops)
738             {
739                 // PreRenderView reach maxLoops - probably a infinitive recursion:
740                 boolean production = facesContext.isProjectStage(ProjectStage.Production);
741                 /*
742                 Level level = production ? Level.FINE : Level.WARNING;
743                 if (log.isLoggable(level))
744                 {
745                     log.log(level, "Cicle over buildView-PreRenderViewEvent on RENDER_RESPONSE phase "
746                                    + "reaches maximal limit, please check listeners for infinite recursion.");
747                 }*/
748             }
749             
750             facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, BUILD_VIEW_CYCLE_STEP);
751         }
752         catch (IOException e)
753         {
754             throw new FacesException(e.getMessage(), e);
755         }
756     }
757     
758     public void executeViewHandlerRender(FacesContext facesContext)
759     {
760         Application application = facesContext.getApplication();
761         ViewHandler viewHandler = application.getViewHandler();
762 
763         try
764         {
765             viewHandler.renderView(facesContext, facesContext.getViewRoot());
766             
767             // log all unhandled FacesMessages, don't swallow them
768             // perf: org.apache.myfaces.context.servlet.FacesContextImpl.getMessageList() creates
769             // new Collections.unmodifiableList with every invocation->  call it only once
770             // and messageList is RandomAccess -> use index based loop
771             List<FacesMessage> messageList = facesContext.getMessageList();
772             if (!messageList.isEmpty())
773             {
774                 StringBuilder builder = new StringBuilder();
775                 //boolean shouldLog = false;
776                 for (int i = 0, size = messageList.size(); i < size; i++)
777                 {
778                     FacesMessage message = messageList.get(i);
779                     if (!message.isRendered())
780                     {
781                         builder.append("\n- ");
782                         builder.append(message.getDetail());
783                         
784                         //shouldLog = true;
785                     }
786                 }
787                 /*
788                 if (shouldLog)
789                 {
790                     log.log(Level.WARNING, "There are some unhandled FacesMessages, " +
791                             "this means not every FacesMessage had a chance to be rendered.\n" +
792                             "These unhandled FacesMessages are: " + builder.toString());
793                 }*/
794             }
795             facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, VIEWHANDLER_RENDER_STEP);
796         }
797         catch (IOException e)
798         {
799             throw new FacesException(e.getMessage(), e);
800         }
801     }
802     
803     public void executeAfterRender(FacesContext facesContext) throws Exception
804     {
805         if (lifecycle instanceof LifecycleImpl)
806         {
807             LifecycleImpl lifecycleImpl = (LifecycleImpl) lifecycle;
808             
809             Object phaseExecutor = null;
810             Field renderExecutorField = lifecycleImpl.getClass().getDeclaredField("renderExecutor");
811             if (!renderExecutorField.isAccessible())
812             {
813                 renderExecutorField.setAccessible(true);
814             }
815             phaseExecutor = renderExecutorField.get(lifecycleImpl);
816             
817             Object phaseManager = facesContext.getAttributes().get(PHASE_MANAGER_INSTANCE);
818             if (phaseManager == null)
819             {
820                 Method getPhaseListenersMethod = lifecycleImpl.getClass().getDeclaredMethod("getPhaseListeners");
821                 if (!getPhaseListenersMethod.isAccessible())
822                 {
823                     getPhaseListenersMethod.setAccessible(true);
824                 }
825                 
826                 Constructor<?> plmc = PHASE_MANAGER_CLASS.getDeclaredConstructor(new Class[]{Lifecycle.class, FacesContext.class, PhaseListener[].class});
827                 if (!plmc.isAccessible())
828                 {
829                     plmc.setAccessible(true);
830                 }
831                 phaseManager = plmc.newInstance(lifecycle, facesContext, getPhaseListenersMethod.invoke(lifecycleImpl, null));
832                 facesContext.getAttributes().put(PHASE_MANAGER_INSTANCE, phaseManager);
833             }
834             
835             
836             Flash flash = facesContext.getExternalContext().getFlash();
837             
838             //phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase());
839             Method informPhaseListenersAfterMethod = phaseManager.getClass().getDeclaredMethod("informPhaseListenersAfter", PhaseId.class);
840             if(!(informPhaseListenersAfterMethod.isAccessible()))
841             {
842                 informPhaseListenersAfterMethod.setAccessible(true);
843             }
844             informPhaseListenersAfterMethod.invoke(phaseManager, PhaseId.RENDER_RESPONSE);
845             
846             flash.doPostPhaseActions(facesContext);
847             
848             facesContext.getExceptionHandler().handle();
849 
850             facesContext.getAttributes().remove(PHASE_MANAGER_INSTANCE);
851             
852             facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, AFTER_RENDER_STEP);
853             //End render response phase
854             facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.RENDER_RESPONSE);
855         }
856         else
857         {
858             throw new UnsupportedOperationException("Cannot execute phase on custom lifecycle instances");
859         }
860     }
861     
862     /**
863      * Execute an specified phase, doing some reflection over LifecycleImpl.
864      * 
865      * @param facesContext
866      * @param phase
867      * @throws Exception
868      */
869     protected void executePhase(FacesContext facesContext, PhaseId phase) throws Exception
870     {
871         if (lifecycle instanceof LifecycleImpl)
872         {
873             LifecycleImpl lifecycleImpl = (LifecycleImpl) lifecycle;
874             
875             int phaseId = phase.equals(PhaseId.RESTORE_VIEW) ? 0 :
876                           phase.equals(PhaseId.APPLY_REQUEST_VALUES) ? 1 : 
877                           phase.equals(PhaseId.PROCESS_VALIDATIONS) ? 2 :
878                           phase.equals(PhaseId.UPDATE_MODEL_VALUES) ? 3 : 
879                           phase.equals(PhaseId.INVOKE_APPLICATION) ? 4 : 5 ;
880             
881             Object phaseExecutor = null;
882             if (phaseId < 5)
883             {
884                 Field lifecycleExecutorsField = lifecycleImpl.getClass().getDeclaredField("lifecycleExecutors");
885                 if (!lifecycleExecutorsField.isAccessible())
886                 {
887                     lifecycleExecutorsField.setAccessible(true);
888                 }
889                 phaseExecutor = ((Object[])lifecycleExecutorsField.get(lifecycleImpl))[phaseId];
890             }
891             else
892             {
893                 Field renderExecutorField = lifecycleImpl.getClass().getDeclaredField("renderExecutor");
894                 if (!renderExecutorField.isAccessible())
895                 {
896                     renderExecutorField.setAccessible(true);
897                 }
898                 phaseExecutor = renderExecutorField.get(lifecycleImpl);
899             }
900             
901             Object phaseManager = facesContext.getAttributes().get(PHASE_MANAGER_INSTANCE);
902             if (phaseManager == null)
903             {
904                 Method getPhaseListenersMethod = lifecycleImpl.getClass().getDeclaredMethod("getPhaseListeners");
905                 if (!getPhaseListenersMethod.isAccessible())
906                 {
907                     getPhaseListenersMethod.setAccessible(true);
908                 }
909                 
910                 Constructor<?> plmc = PHASE_MANAGER_CLASS.getDeclaredConstructor(new Class[]{Lifecycle.class, FacesContext.class, PhaseListener[].class});
911                 if (!plmc.isAccessible())
912                 {
913                     plmc.setAccessible(true);
914                 }
915                 phaseManager = plmc.newInstance(lifecycle, facesContext, getPhaseListenersMethod.invoke(lifecycleImpl, null));
916                 facesContext.getAttributes().put(PHASE_MANAGER_INSTANCE, phaseManager);
917             }
918             
919             Method executePhaseMethod = lifecycleImpl.getClass().getDeclaredMethod("executePhase", new Class[]{
920                     FacesContext.class, PHASE_EXECUTOR_CLASS, PHASE_MANAGER_CLASS});
921             if (!executePhaseMethod.isAccessible())
922             {
923                 executePhaseMethod.setAccessible(true);
924             }
925             
926             executePhaseMethod.invoke(lifecycleImpl, facesContext, phaseExecutor, phaseManager);
927             
928             if (phase.equals(PhaseId.RENDER_RESPONSE))
929             {
930                 facesContext.getAttributes().remove(PHASE_MANAGER_INSTANCE);
931             }
932         }
933         else
934         {
935             throw new UnsupportedOperationException("Cannot execute phase on custom lifecycle instances");
936         }
937     }
938     
939     protected String getRenderedContent(FacesContext facesContext) throws IOException
940     {
941         MockPrintWriter writer1 = (MockPrintWriter) (((HttpServletResponse) facesContext.getExternalContext().getResponse()).getWriter());
942         return String.valueOf(writer1.content());
943     }
944 
945     // ------------------------------------------------------ Instance Variables
946 
947 
948     // Thread context class loader saved and restored after each test
949     private ClassLoader threadContextClassLoader = null;
950 
951     // Servlet objects 
952     protected MockServletConfig servletConfig = null;
953     protected MockServletContext servletContext = null;
954 
955     // MyFaces specific objects created by the servlet environment
956     protected StartupServletContextListener listener = null;
957     protected FacesConfigurationProvider facesConfigurationProvider = null;
958     
959     protected FacesContextFactory facesContextFactory = null;
960     protected LifecycleFactory lifecycleFactory = null;
961     protected Lifecycle lifecycle;
962 
963     private static FacesConfig standardFacesConfig;
964 
965     // ------------------------------------------------------ Subclasses
966 
967     /**
968      * Mock FacesConfigurationProvider that replace the original ViewDeclarationLanguageFactory with a customized one that
969      * contains only facelets vdl and cache some FacesConfig that does not change to reduce the time required to process each test.
970      * 
971      * @author Leonardo Uribe
972      *
973      */
974     protected class MyFacesMockFacesConfigurationProvider extends DefaultFacesConfigurationProvider
975     {
976         private AbstractMyFacesTestCase testCase;
977         
978         public MyFacesMockFacesConfigurationProvider(AbstractMyFacesTestCase testCase)
979         {
980             this.testCase = testCase;
981         }
982         
983         @Override
984         public FacesConfig getStandardFacesConfig(ExternalContext ectx)
985         {
986             if (standardFacesConfig == null)
987             {
988                 FacesConfig sfc = super.getStandardFacesConfig(ectx);
989                 Factory factory = (Factory) sfc.getFactories().get(0);
990                 // Override the default vdl factory with a mock one that only load
991                 // facelet views
992                 factory.getViewDeclarationLanguageFactory().set(0, MockMyFacesViewDeclarationLanguageFactory.class.getName());
993                 standardFacesConfig = sfc;
994             }
995             return standardFacesConfig;
996         }
997 
998         @Override
999         public FacesConfig getAnnotationsFacesConfig(ExternalContext ectx,
1000                 boolean metadataComplete)
1001         {
1002             FacesConfig facesConfig = null;
1003             if (isScanAnnotations())
1004             {
1005                 facesConfig = super.getAnnotationsFacesConfig(ectx, metadataComplete); 
1006             }
1007             
1008             ManagedBeans annoManagedBeans = testCase.getClass().getAnnotation(ManagedBeans.class);
1009             if (annoManagedBeans != null)
1010             {
1011                 if (facesConfig == null)
1012                 {
1013                     facesConfig = new org.apache.myfaces.config.impl.digester.elements.FacesConfig();
1014                 }
1015                 for (PageBean annoPageBean : annoManagedBeans.values())
1016                 {
1017                     org.apache.myfaces.config.impl.digester.elements.ManagedBean bean = new 
1018                         org.apache.myfaces.config.impl.digester.elements.ManagedBean();
1019                     bean.setBeanClass(annoPageBean.clazz().getName());
1020                     bean.setName(annoPageBean.name() == null ? annoPageBean.clazz().getName() : annoPageBean.name());
1021                     bean.setScope(annoPageBean.scope() == null ? "request" : annoPageBean.scope());
1022                     bean.setEager(Boolean.toString(annoPageBean.eager()));
1023                     
1024                     ((org.apache.myfaces.config.impl.digester.elements.FacesConfig)facesConfig).addManagedBean(bean);
1025                 }
1026             }
1027 
1028             PageBean annoPageBean = testCase.getClass().getAnnotation(PageBean.class);
1029             if (annoPageBean != null)
1030             {
1031                 if (facesConfig == null)
1032                 {
1033                     facesConfig = new org.apache.myfaces.config.impl.digester.elements.FacesConfig();
1034                 }
1035                 org.apache.myfaces.config.impl.digester.elements.ManagedBean bean = new 
1036                     org.apache.myfaces.config.impl.digester.elements.ManagedBean();
1037                 bean.setBeanClass(annoPageBean.clazz().getName());
1038                 bean.setName(annoPageBean.name() == null ? annoPageBean.clazz().getName() : annoPageBean.name());
1039                 bean.setScope(annoPageBean.scope() == null ? "request" : annoPageBean.scope());
1040                 bean.setEager(Boolean.toString(annoPageBean.eager()));
1041                 
1042                 ((org.apache.myfaces.config.impl.digester.elements.FacesConfig)facesConfig).addManagedBean(bean);
1043             }
1044             return facesConfig;
1045         }
1046         
1047         @Override
1048         public List<FacesConfig> getContextSpecifiedFacesConfig(ExternalContext ectx)
1049         {
1050             List<FacesConfig> appConfigResources = super.getContextSpecifiedFacesConfig(ectx);
1051             
1052             DeclareFacesConfig annoFacesConfig = testCase.getClass().getAnnotation(DeclareFacesConfig.class);
1053             if (annoFacesConfig != null)
1054             {
1055                 Logger log = Logger.getLogger(testCase.getClass().getName());
1056                 try
1057                 {
1058                     for (String systemId : annoFacesConfig.value())
1059                     {
1060                         if (MyfacesConfig.getCurrentInstance(ectx).isValidateXML())
1061                         {
1062                             URL url = ectx.getResource(systemId);
1063                             if (url != null)
1064                             {
1065                                 validateFacesConfig(ectx, url);
1066                             }
1067                         }   
1068                         InputStream stream = ectx.getResourceAsStream(systemId);
1069                         if (stream == null)
1070                         {
1071                             
1072                             log.severe("Faces config resource " + systemId + " not found");
1073                             continue;
1074                         }
1075             
1076                         if (log.isLoggable(Level.INFO))
1077                         {
1078                             log.info("Reading config " + systemId);
1079                         }
1080                         appConfigResources.add(getUnmarshaller(ectx).getFacesConfig(stream, systemId));
1081                         //getDispenser().feed(getUnmarshaller().getFacesConfig(stream, systemId));
1082                         stream.close();
1083     
1084                     }
1085                 }
1086                 catch (Throwable e)
1087                 {
1088                     throw new FacesException(e);
1089                 }
1090             }
1091             return appConfigResources;
1092         }
1093     }
1094     
1095     private void validateFacesConfig(ExternalContext ectx, URL url) throws IOException, SAXException
1096     {
1097         String version = ConfigFilesXmlValidationUtils.getFacesConfigVersion(url);
1098         if ("1.2".equals(version) || "2.0".equals(version) || "2.1".equals(version))
1099         {
1100             ConfigFilesXmlValidationUtils.validateFacesConfigFile(url, ectx, version);
1101         }
1102     }
1103 }