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.context.servlet;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.EnumSet;
26  import java.util.List;
27  import java.util.Set;
28  import java.util.logging.Level;
29  import java.util.logging.Logger;
30  
31  import javax.faces.FactoryFinder;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UIViewParameter;
34  import javax.faces.component.UIViewRoot;
35  import javax.faces.component.html.HtmlBody;
36  import javax.faces.component.html.HtmlHead;
37  import javax.faces.component.visit.VisitCallback;
38  import javax.faces.component.visit.VisitContext;
39  import javax.faces.component.visit.VisitContextFactory;
40  import javax.faces.component.visit.VisitHint;
41  import javax.faces.component.visit.VisitResult;
42  import javax.faces.context.ExternalContext;
43  import javax.faces.context.FacesContext;
44  import javax.faces.context.PartialResponseWriter;
45  import javax.faces.context.PartialViewContext;
46  import javax.faces.context.ResponseWriter;
47  import javax.faces.event.PhaseId;
48  import javax.faces.lifecycle.ClientWindow;
49  import javax.faces.render.RenderKit;
50  import javax.faces.render.RenderKitFactory;
51  import javax.faces.view.ViewMetadata;
52  
53  import org.apache.myfaces.context.PartialResponseWriterImpl;
54  import org.apache.myfaces.context.RequestViewContext;
55  import org.apache.myfaces.renderkit.html.HtmlResponseStateManager;
56  import org.apache.myfaces.shared.config.MyfacesConfig;
57  import org.apache.myfaces.shared.util.ExternalContextUtils;
58  import org.apache.myfaces.shared.util.StringUtils;
59  
60  public class PartialViewContextImpl extends PartialViewContext
61  {
62  
63      private static final String FACES_REQUEST = "Faces-Request";
64      private static final String PARTIAL_AJAX = "partial/ajax";
65      private static final String PARTIAL_AJAX_REQ = "javax.faces.partial.ajax";
66      private static final String PARTIAL_PROCESS = "partial/process";
67      private static final String SOURCE_PARAM_NAME = "javax.faces.source";
68      private static final String JAVAX_FACES_REQUEST = "javax.faces.request";
69  
70      /**
71       * Internal extension for
72       * https://issues.apache.org/jira/browse/MYFACES-2841
73       * will be changed for 2.1 to the official marker
74       */
75      private static final String PARTIAL_IFRAME = "org.apache.myfaces.partial.iframe";
76      
77      private static final  Set<VisitHint> PARTIAL_EXECUTE_HINTS = Collections.unmodifiableSet( 
78              EnumSet.of(VisitHint.EXECUTE_LIFECYCLE, VisitHint.SKIP_UNRENDERED));
79      
80      // unrendered have to be skipped, transient definitely must be added to our list!
81      private static final  Set<VisitHint> PARTIAL_RENDER_HINTS = 
82              Collections.unmodifiableSet(EnumSet.of(VisitHint.SKIP_UNRENDERED));
83  
84      private FacesContext _facesContext = null;
85      private boolean _released = false;
86      // Cached values, since their parent methods could be called
87      // many times and the result does not change during the life time
88      // of this object.
89      private Boolean _ajaxRequest = null;
90  
91      /**
92       * Internal extension for
93       * https://issues.apache.org/jira/browse/MYFACES-2841
94       * will be changed for 2.1 to the official marker
95       */
96      private Boolean _iframeRequest = null;
97  
98      private Collection<String> _executeClientIds = null;
99      private Collection<String> _renderClientIds = null;
100     // Values that need to be saved because exists a setXX method 
101     private Boolean _partialRequest = null;
102     private Boolean _renderAll = null;
103     private PartialResponseWriter _partialResponseWriter = null;
104     private VisitContextFactory _visitContextFactory = null;
105     private Boolean _resetValues = null;
106 
107     public PartialViewContextImpl(FacesContext context)
108     {
109         _facesContext = context;
110     }
111     
112     public PartialViewContextImpl(FacesContext context, 
113             VisitContextFactory visitContextFactory)
114     {
115         _facesContext = context;
116         _visitContextFactory = visitContextFactory;
117     }
118 
119     @Override
120     public boolean isAjaxRequest()
121     {
122         assertNotReleased();
123         if (_ajaxRequest == null)
124         {
125             String requestType = _facesContext.getExternalContext().
126                    getRequestHeaderMap().get(FACES_REQUEST);
127             _ajaxRequest = (requestType != null && PARTIAL_AJAX.equals(requestType));
128             String reqParmamterPartialAjax = _facesContext.getExternalContext().
129                     getRequestParameterMap().get(PARTIAL_AJAX_REQ);
130             //jsdoc reference in an ajax request the javax.faces.partial.ajax must be set as ajax parameter
131             //the other one is Faces-Request == partial/ajax which is basically the same
132             _ajaxRequest = _ajaxRequest || reqParmamterPartialAjax != null;
133         }
134         return _ajaxRequest;
135     }
136 
137     @Override
138     public boolean isExecuteAll()
139     {
140         assertNotReleased();
141 
142         if (isAjaxRequest())
143         {
144             String executeMode = _facesContext.getExternalContext().
145                     getRequestParameterMap().get(
146                     PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
147             if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode))
148             {
149                 return true;
150             }
151         }
152         return false;
153     }
154 
155     @Override
156     public boolean isPartialRequest()
157     {
158         assertNotReleased();
159 
160         if (_partialRequest == null)
161         {
162             String requestType = _facesContext.getExternalContext().
163                     getRequestHeaderMap().get(FACES_REQUEST);
164             _partialRequest = (requestType != null && PARTIAL_PROCESS.equals(requestType));
165         }
166         return _partialRequest || isAjaxRequest();
167     }
168 
169     @Override
170     public boolean isRenderAll()
171     {
172         assertNotReleased();
173 
174         if (_renderAll == null)
175         {
176             if (isAjaxRequest())
177             {
178                 String executeMode = _facesContext.getExternalContext().
179                         getRequestParameterMap().get(
180                         PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
181                 if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode))
182                 {
183                     _renderAll = true;
184                 }
185             }
186             if (_renderAll == null)
187             {
188                 _renderAll = false;
189             }
190         }
191         return _renderAll;
192     }
193 
194     /**
195      * Extension for
196      * https://issues.apache.org/jira/browse/MYFACES-2841
197      * internal extension which detects that the submit is an iframe request
198      * will be changed for the official version which will come in 2.1
199      *
200      * @return true if the current request is an iframe based ajax request
201      */
202     public boolean isIFrameRequest()
203     {
204         if (_iframeRequest == null)
205         {
206             _iframeRequest = _facesContext.getExternalContext().getRequestParameterMap().containsKey(PARTIAL_IFRAME);
207         }
208         return _iframeRequest;
209     }
210 
211     @Override
212     public void setPartialRequest(boolean isPartialRequest)
213     {
214         assertNotReleased();
215 
216         _partialRequest = isPartialRequest;
217 
218     }
219 
220     @Override
221     public void setRenderAll(boolean renderAll)
222     {
223         assertNotReleased();
224 
225         _renderAll = renderAll;
226     }
227 
228     @Override
229     public Collection<String> getExecuteIds()
230     {
231         assertNotReleased();
232 
233         if (_executeClientIds == null)
234         {
235             String executeMode = _facesContext.getExternalContext().
236                     getRequestParameterMap().get(
237                     PartialViewContext.PARTIAL_EXECUTE_PARAM_NAME);
238 
239             if (executeMode != null && !"".equals(executeMode) &&
240                     //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode) &&
241                     !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(executeMode))
242             {
243 
244                 String[] clientIds
245                         = StringUtils.splitShortString(_replaceTabOrEnterCharactersWithSpaces(executeMode), ' ');
246 
247                 //The collection must be mutable
248                 List<String> tempList = new ArrayList<String>();
249                 for (String clientId : clientIds)
250                 {
251                     if (clientId.length() > 0)
252                     {
253                         tempList.add(clientId);
254                     }
255                 }
256                 // The "javax.faces.source" parameter needs to be added to the list of
257                 // execute ids if missing (otherwise, we'd never execute an action associated
258                 // with, e.g., a button).
259 
260                 String source = _facesContext.getExternalContext().getRequestParameterMap().get
261                         (PartialViewContextImpl.SOURCE_PARAM_NAME);
262 
263                 if (source != null)
264                 {
265                     source = source.trim();
266 
267                     if (!tempList.contains(source))
268                     {
269                         tempList.add(source);
270                     }
271                 }
272 
273                 _executeClientIds = tempList;
274             }
275             else
276             {
277                 _executeClientIds = new ArrayList<String>();
278             }
279         }
280         return _executeClientIds;
281     }
282 
283     private String _replaceTabOrEnterCharactersWithSpaces(String mode)
284     {
285         StringBuilder builder = new StringBuilder(mode.length());
286         for (int i = 0; i < mode.length(); i++)
287         {
288             if (mode.charAt(i) == '\t' ||
289                     mode.charAt(i) == '\n')
290             {
291                 builder.append(' ');
292             }
293             else
294             {
295                 builder.append(mode.charAt(i));
296             }
297         }
298         return builder.toString();
299     }
300 
301     @Override
302     public Collection<String> getRenderIds()
303     {
304         assertNotReleased();
305 
306         if (_renderClientIds == null)
307         {
308             String renderMode = _facesContext.getExternalContext().
309                     getRequestParameterMap().get(
310                     PartialViewContext.PARTIAL_RENDER_PARAM_NAME);
311 
312             if (renderMode != null && !"".equals(renderMode) &&
313                     //!PartialViewContext.NO_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode) &&
314                     !PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode))
315             {
316                 String[] clientIds
317                         = StringUtils.splitShortString(_replaceTabOrEnterCharactersWithSpaces(renderMode), ' ');
318 
319                 //The collection must be mutable
320                 List<String> tempList = new ArrayList<String>();
321                 for (String clientId : clientIds)
322                 {
323                     if (clientId.length() > 0)
324                     {
325                         tempList.add(clientId);
326                     }
327                 }
328                 _renderClientIds = tempList;
329             }
330             else
331             {
332                 _renderClientIds = new ArrayList<String>();
333 
334                 if (PartialViewContext.ALL_PARTIAL_PHASE_CLIENT_IDS.equals(renderMode))
335                 {
336                     _renderClientIds.add(PartialResponseWriter.RENDER_ALL_MARKER);
337                 }
338             }
339         }
340         return _renderClientIds;
341     }
342 
343     @Override
344     public PartialResponseWriter getPartialResponseWriter()
345     {
346         assertNotReleased();
347 
348         if (_partialResponseWriter == null)
349         {
350             ResponseWriter responseWriter = _facesContext.getResponseWriter();
351             if (responseWriter == null)
352             {
353                 // This case happens when getPartialResponseWriter() is called before
354                 // render phase, like in ExternalContext.redirect(). We have to create a
355                 // ResponseWriter from the RenderKit and then wrap if necessary. 
356                 try
357                 {
358                     RenderKit renderKit = _facesContext.getRenderKit();
359                     if (renderKit == null)
360                     {
361                         // If the viewRoot was set to null by some reason, or there is no 
362                         // renderKitId on that view, this could be still an ajax redirect,
363                         // so we have to try to calculate the renderKitId and return a 
364                         // RenderKit instance, to send the response.
365                         String renderKitId
366                                 = _facesContext.getApplication().getViewHandler().calculateRenderKitId(_facesContext);
367                         RenderKitFactory rkf
368                                 = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
369                         renderKit = rkf.getRenderKit(_facesContext, renderKitId);
370                     }
371                     responseWriter = renderKit.createResponseWriter(
372                             _facesContext.getExternalContext().getResponseOutputWriter(), "text/xml",
373                             _facesContext.getExternalContext().getRequestCharacterEncoding());
374                 }
375                 catch (IOException e)
376                 {
377                     throw new IllegalStateException("Cannot create Partial Response Writer", e);
378                 }
379             }
380             // It is possible that the RenderKit return a PartialResponseWriter instance when 
381             // createResponseWriter,  so we should cast here for it and prevent double wrapping.
382             if (responseWriter instanceof PartialResponseWriter)
383             {
384                 _partialResponseWriter = (PartialResponseWriter) responseWriter;
385             }
386             else
387             {
388                 _partialResponseWriter = new PartialResponseWriterImpl(responseWriter);
389             }
390         }
391         return _partialResponseWriter;
392     }
393 
394     /**
395      * process the partial response
396      * allowed phase ids according to the spec
397      *
398      *
399      */
400     @Override
401     public void processPartial(PhaseId phaseId)
402     {
403         assertNotReleased();
404 
405         UIViewRoot viewRoot = _facesContext.getViewRoot();
406 
407         if (phaseId == PhaseId.APPLY_REQUEST_VALUES
408                 || phaseId == PhaseId.PROCESS_VALIDATIONS
409                 || phaseId == PhaseId.UPDATE_MODEL_VALUES)
410         {
411             processPartialExecute(viewRoot, phaseId);
412         }
413         else if (phaseId == PhaseId.RENDER_RESPONSE)
414         {
415             processPartialRendering(viewRoot, phaseId);
416         }
417     }
418 
419     private void processPartialExecute(UIViewRoot viewRoot, PhaseId phaseId)
420     {
421         PartialViewContext pvc = _facesContext.getPartialViewContext();
422         Collection<String> executeIds = pvc.getExecuteIds();
423         if (executeIds == null || executeIds.isEmpty())
424         {
425             return;
426         }
427         
428         VisitContext visitCtx = getVisitContextFactory().getVisitContext(_facesContext, executeIds, 
429                 PARTIAL_EXECUTE_HINTS);
430         viewRoot.visitTree(visitCtx, new PhaseAwareVisitCallback(_facesContext, phaseId));
431     }
432 
433     private void processPartialRendering(UIViewRoot viewRoot, PhaseId phaseId)
434     {
435         //TODO process partial rendering
436         //https://issues.apache.org/jira/browse/MYFACES-2118
437         //Collection<String> renderIds = getRenderIds();
438 
439         // We need to always update the view state marker when processing partial
440         // rendering, because there is no way to check when the state has been changed
441         // or not. Anyway, if we return empty response, according to the spec a javascript
442         // message displayed, so we need to return something.
443         //if (renderIds == null || renderIds.isEmpty()) {
444         //    return;
445         //}
446 
447         // note that we cannot use this.getPartialResponseWriter(), because
448         // this could cause problems if PartialResponseWriter is wrapped
449         PartialResponseWriter writer = _facesContext.getPartialViewContext().getPartialResponseWriter();
450         PartialViewContext pvc = _facesContext.getPartialViewContext();
451 
452         ResponseWriter oldWriter = _facesContext.getResponseWriter();
453         boolean inDocument = false;
454 
455         //response type = text/xml
456         //no caching and no timeout if possible!
457         ExternalContext externalContext = _facesContext.getExternalContext();
458         externalContext.setResponseContentType("text/xml");
459         externalContext.addResponseHeader("Pragma", "no-cache");
460         externalContext.addResponseHeader("Cache-control", "no-cache");
461         //under normal circumstances pragma should be enough, IE needs
462         //a special treatment!
463         //http://support.microsoft.com/kb/234067
464         externalContext.addResponseHeader("Expires", "-1");
465 
466         try
467         {
468             String currentEncoding = writer.getCharacterEncoding();
469             writer.writePreamble("<?xml version=\"1.0\" encoding=\""+
470                 (currentEncoding == null ? "UTF-8" : currentEncoding) +"\"?>");
471             writer.startDocument();
472             
473             writer.writeAttribute("id", viewRoot.getContainerClientId(_facesContext),"id");
474             
475             inDocument = true;
476             _facesContext.setResponseWriter(writer);
477             
478             if (isResetValues())
479             {
480                 viewRoot.resetValues(_facesContext, getRenderIds());
481             }
482 
483             if (pvc.isRenderAll())
484             {
485                 processRenderAll(viewRoot, writer);
486             }
487             else
488             {
489                 Collection<String> renderIds = pvc.getRenderIds();
490                 //Only apply partial visit if we have ids to traverse
491                 if (renderIds != null && !renderIds.isEmpty())
492                 {
493                     // render=@all, so output the body.
494                     if (renderIds.contains(PartialResponseWriter.RENDER_ALL_MARKER))
495                     {
496                         processRenderAll(viewRoot, writer);
497                     }
498                     else
499                     {
500                         List<UIComponent> updatedComponents = null;
501                         if (!ExternalContextUtils.isPortlet(_facesContext.getExternalContext()) &&
502                                 MyfacesConfig.getCurrentInstance(externalContext).isStrictJsf2RefreshTargetAjax())
503                         {
504                             RequestViewContext rvc = RequestViewContext.getCurrentInstance(_facesContext);
505                             if (rvc.isRenderTarget("head"))
506                             {
507                                 UIComponent head = findHeadComponent(viewRoot);
508                                 if (head != null)
509                                 {
510                                     writer.startUpdate("javax.faces.ViewHead");
511                                     head.encodeAll(_facesContext);
512                                     writer.endUpdate();
513                                     if (updatedComponents == null)
514                                     {
515                                         updatedComponents = new ArrayList<UIComponent>();
516                                     }
517                                     updatedComponents.add(head);
518                                 }
519                             }
520                             if (rvc.isRenderTarget("body") || rvc.isRenderTarget("form"))
521                             {
522                                 UIComponent body = findBodyComponent(viewRoot);
523                                 if (body != null)
524                                 {
525                                     writer.startUpdate("javax.faces.ViewBody");
526                                     body.encodeAll(_facesContext);
527                                     writer.endUpdate();
528                                     if (updatedComponents == null)
529                                     {
530                                         updatedComponents = new ArrayList<UIComponent>();
531                                     }
532                                     updatedComponents.add(body);
533                                 }
534                             }
535                         }
536 
537                         VisitContext visitCtx = getVisitContextFactory().getVisitContext(
538                                 _facesContext, renderIds, PARTIAL_RENDER_HINTS);
539                         viewRoot.visitTree(visitCtx,
540                                            new PhaseAwareVisitCallback(_facesContext, phaseId, updatedComponents));
541                     }
542                 }
543                 else if (!ExternalContextUtils.isPortlet(_facesContext.getExternalContext()) &&
544                         MyfacesConfig.getCurrentInstance(externalContext).isStrictJsf2RefreshTargetAjax())
545                 {
546                     RequestViewContext rvc = RequestViewContext.getCurrentInstance(_facesContext);
547                     if (rvc.isRenderTarget("head"))
548                     {
549                         UIComponent head = findHeadComponent(viewRoot);
550                         if (head != null)
551                         {
552                             writer.startUpdate("javax.faces.ViewHead");
553                             head.encodeAll(_facesContext);
554                             writer.endUpdate();
555                         }
556                     }
557                     if (rvc.isRenderTarget("body") || rvc.isRenderTarget("form"))
558                     {
559                         UIComponent body = findBodyComponent(viewRoot);
560                         if (body != null)
561                         {
562                             writer.startUpdate("javax.faces.ViewBody");
563                             body.encodeAll(_facesContext);
564                             writer.endUpdate();
565                         }
566                     }
567                 }
568             }
569 
570             // invoke encodeAll() on every UIViewParameter in the view to 
571             // enable every UIViewParameter to save its value in the state
572             // just like UIViewRoot.encodeEnd() does on a normal request
573             // (see MYFACES-2645 for details)
574             Collection<UIViewParameter> viewParams = ViewMetadata.getViewParameters(viewRoot);
575             if (!viewParams.isEmpty())
576             {
577                 for (UIViewParameter param : viewParams)
578                 {
579                     param.encodeAll(_facesContext);
580                 }
581             }
582 
583             //Retrieve the state and apply it if it is not null.
584             String viewState = _facesContext.getApplication().getStateManager().getViewState(_facesContext);
585             if (viewState != null)
586             {
587                 writer.startUpdate(HtmlResponseStateManager.generateUpdateViewStateId(
588                     _facesContext));
589                 writer.write(viewState);
590                 writer.endUpdate();
591             }
592             else if (viewRoot.isTransient())
593             {
594                 //TODO: fix javascript side, so the field is not removed on ajax form update
595                 writer.startUpdate(HtmlResponseStateManager.generateUpdateViewStateId(
596                     _facesContext));
597                 writer.write("stateless");
598                 writer.endUpdate();
599                 //END TODO
600             }
601             
602             
603             ClientWindow cw = _facesContext.getExternalContext().getClientWindow();
604             if (cw != null)
605             {
606                 writer.startUpdate(HtmlResponseStateManager.generateUpdateClientWindowId(
607                     _facesContext));
608                 writer.write(cw.getId());
609                 writer.endUpdate();
610             }
611         }
612         catch (IOException ex)
613         {
614             Logger log = Logger.getLogger(PartialViewContextImpl.class.getName());
615             if (log.isLoggable(Level.SEVERE))
616             {
617                 log.log(Level.SEVERE, "", ex);
618             }
619 
620         }
621         finally
622         {
623             try
624             {
625                 if (inDocument)
626                 {
627                     writer.endDocument();
628                 }
629                 writer.flush();
630             }
631             catch (IOException ex)
632             {
633                 Logger log = Logger.getLogger(PartialViewContextImpl.class.getName());
634                 if (log.isLoggable(Level.SEVERE))
635                 {
636                     log.log(Level.SEVERE, "", ex);
637                 }
638             }
639 
640             _facesContext.setResponseWriter(oldWriter);
641         }
642 
643     }
644 
645     private void processRenderAll(UIViewRoot viewRoot, PartialResponseWriter writer) throws IOException
646     {
647         //java.util.Iterator<UIComponent> iter = viewRoot.getFacetsAndChildren();
648         writer.startUpdate(PartialResponseWriter.RENDER_ALL_MARKER);
649         //while (iter.hasNext()) 
650         //{ 
651         //UIComponent comp = iter.next();
652 
653         //TODO: Do not check for a specific instance,
654         //just render all children.
655         //if (comp instanceof javax.faces.component.html.HtmlBody)
656         //{
657         //comp.encodeAll (_facesContext);
658         //}
659         //}
660         for (int i = 0, childCount = viewRoot.getChildCount(); i < childCount; i++)
661         {
662             UIComponent comp = viewRoot.getChildren().get(i);
663             comp.encodeAll(_facesContext);
664         }
665         writer.endUpdate();
666     }
667 
668     /**
669      * has to be thrown in many of the methods if the method is called after the instance has been released!
670      */
671     private void assertNotReleased()
672     {
673         if (_released)
674         {
675             throw new IllegalStateException("Error the FacesContext is already released!");
676         }
677     }
678 
679     @Override
680     public void release()
681     {
682         assertNotReleased();
683         _visitContextFactory = null;
684         _executeClientIds = null;
685         _renderClientIds = null;
686         _ajaxRequest = null;
687         _partialRequest = null;
688         _renderAll = null;
689         _facesContext = null;
690         _released = true;
691     }
692 
693     private UIComponent findHeadComponent(UIViewRoot root)
694     {
695         for (UIComponent child : root.getChildren())
696         {
697             if (child instanceof HtmlHead)
698             {
699                 return child;
700             }
701             else
702             {
703                 for (UIComponent grandchild : child.getChildren())
704                 {
705                     if (grandchild instanceof HtmlHead)
706                     {
707                         return grandchild;
708                     }
709                 }
710             }
711         }
712         return null;
713     }
714 
715     private UIComponent findBodyComponent(UIViewRoot root)
716     {
717         for (UIComponent child : root.getChildren())
718         {
719             if (child instanceof HtmlBody)
720             {
721                 return child;
722             }
723             else
724             {
725                 for (UIComponent grandchild : child.getChildren())
726                 {
727                     if (grandchild instanceof HtmlBody)
728                     {
729                         return grandchild;
730                     }
731                 }
732             }
733         }
734         return null;
735     }
736     
737     private VisitContextFactory getVisitContextFactory()
738     {
739         if (_visitContextFactory == null)
740         {
741             _visitContextFactory = (VisitContextFactory)FactoryFinder.getFactory(FactoryFinder.VISIT_CONTEXT_FACTORY);
742         }
743         return _visitContextFactory;
744     }
745 
746     @Override
747     public boolean isResetValues()
748     {
749         if (_resetValues == null)
750         {
751             String value = _facesContext.getExternalContext().getRequestParameterMap().
752                 get(RESET_VALUES_PARAM_NAME);
753             _resetValues = "true".equals(value);
754         }
755         return _resetValues;
756     }
757 
758     private class PhaseAwareVisitCallback implements VisitCallback
759     {
760 
761         private PhaseId _phaseId;
762         private FacesContext _facesContext;
763         private List<UIComponent> _alreadyUpdatedComponents;
764 
765         public PhaseAwareVisitCallback(FacesContext facesContext, PhaseId phaseId)
766         {
767             this._phaseId = phaseId;
768             this._facesContext = facesContext;
769             this._alreadyUpdatedComponents = null;
770         }
771 
772         public PhaseAwareVisitCallback(FacesContext facesContext, PhaseId phaseId,
773                                        List<UIComponent> alreadyUpdatedComponents)
774         {
775             this._phaseId = phaseId;
776             this._facesContext = facesContext;
777             this._alreadyUpdatedComponents = alreadyUpdatedComponents;
778         }
779 
780         public VisitResult visit(VisitContext context, UIComponent target)
781         {
782             if (_phaseId == PhaseId.APPLY_REQUEST_VALUES)
783             {
784                 target.processDecodes(_facesContext);
785             }
786             else if (_phaseId == PhaseId.PROCESS_VALIDATIONS)
787             {
788                 target.processValidators(_facesContext);
789             }
790             else if (_phaseId == PhaseId.UPDATE_MODEL_VALUES)
791             {
792                 target.processUpdates(_facesContext);
793             }
794             else if (_phaseId == PhaseId.RENDER_RESPONSE)
795             {
796                 processRenderComponent(target);
797             }
798             else
799             {
800                 throw new IllegalStateException("PPR Response, illegale phase called");
801             }
802 
803             // Return VisitResult.REJECT as processDecodes/Validators/Updates already traverse sub tree
804             return VisitResult.REJECT;
805         }
806 
807         /**
808          * the rendering subpart of the tree walker
809          * every component id which is passed down via render must be handled
810          * here!
811          *
812          * @param target the target component to be handled!
813          */
814         private void processRenderComponent(UIComponent target)
815         {
816             boolean inUpdate = false;
817             PartialResponseWriter writer = (PartialResponseWriter) _facesContext.getResponseWriter();
818             if (this._alreadyUpdatedComponents != null)
819             {
820                 //Check if the parent was already updated.
821                 UIComponent parent = target;
822                 while (parent != null)
823                 {
824                     if (this._alreadyUpdatedComponents.contains(parent))
825                     {
826                         return;
827                     }
828                     parent = parent.getParent();
829                 }
830             }
831             try
832             {
833                 writer.startUpdate(target.getClientId(_facesContext));
834                 inUpdate = true;
835                 target.encodeAll(_facesContext);
836             }
837             catch (IOException ex)
838             {
839                 Logger log = Logger.getLogger(PartialViewContextImpl.class.getName());
840                 if (log.isLoggable(Level.SEVERE))
841                 {
842                     log.log(Level.SEVERE, "IOException for rendering component", ex);
843                 }
844             }
845             finally
846             {
847                 if (inUpdate)
848                 {
849                     try
850                     {
851                         writer.endUpdate();
852                     }
853                     catch (IOException ex)
854                     {
855                         Logger log = Logger.getLogger(PartialViewContextImpl.class.getName());
856                         if (log.isLoggable(Level.SEVERE))
857                         {
858                             log.log(Level.SEVERE, "IOException for rendering component, stopping update rendering", ex);
859                         }
860                     }
861                 }
862             }
863         }
864     }
865 }