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.view.facelets;
20  
21  import java.io.Serializable;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.EnumSet;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.faces.FacesException;
32  import javax.faces.FactoryFinder;
33  import javax.faces.application.ProjectStage;
34  import javax.faces.application.StateManager;
35  import javax.faces.component.ContextCallback;
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIComponentBase;
38  import javax.faces.component.UIViewRoot;
39  import javax.faces.component.visit.VisitCallback;
40  import javax.faces.component.visit.VisitContext;
41  import javax.faces.component.visit.VisitContextFactory;
42  import javax.faces.component.visit.VisitHint;
43  import javax.faces.component.visit.VisitResult;
44  import javax.faces.context.FacesContext;
45  import javax.faces.event.PostAddToViewEvent;
46  import javax.faces.event.PreRemoveFromViewEvent;
47  import javax.faces.event.SystemEvent;
48  import javax.faces.event.SystemEventListener;
49  import javax.faces.render.RenderKitFactory;
50  import javax.faces.render.ResponseStateManager;
51  import javax.faces.view.StateManagementStrategy;
52  import javax.faces.view.ViewDeclarationLanguage;
53  import javax.faces.view.ViewDeclarationLanguageFactory;
54  import javax.faces.view.ViewMetadata;
55  
56  import org.apache.myfaces.application.StateManagerImpl;
57  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
58  import org.apache.myfaces.context.RequestViewContext;
59  import org.apache.myfaces.shared.config.MyfacesConfig;
60  import org.apache.myfaces.shared.util.ClassUtils;
61  import org.apache.myfaces.shared.util.HashMapUtils;
62  import org.apache.myfaces.shared.util.WebConfigParamUtils;
63  import org.apache.myfaces.view.facelets.compiler.CheckDuplicateIdFaceletUtils;
64  import org.apache.myfaces.view.facelets.pool.ViewEntry;
65  import org.apache.myfaces.view.facelets.pool.ViewPool;
66  import org.apache.myfaces.view.facelets.pool.ViewStructureMetadata;
67  import org.apache.myfaces.view.facelets.tag.jsf.ComponentSupport;
68  import org.apache.myfaces.view.facelets.tag.jsf.FaceletState;
69  
70  /**
71   * This class implements partial state saving feature when facelets
72   * is used to render pages. (Theorically it could be applied on jsp case too,
73   * but all considerations below should be true before apply it).
74   * 
75   * The following considerations apply for this class:
76   * 
77   * 1. This StateManagementStrategy should only be active if javax.faces.PARTIAL_STATE_SAVING
78   *    config param is active(true). See javadoc on StateManager for details.
79   * 2. A map using component clientId as keys are used to hold the state.
80   * 3. Each component has a valid id after ViewDeclarationLanguage.buildView().
81   *    This implies that somewhere, every TagHandler that create an UIComponent 
82   *    instance should call setId and assign it.
83   * 4. Every TagHandler that create an UIComponent instance should call markInitialState
84   *    after the component is populated. Otherwise, full state is always saved.
85   * 5. A SystemEventListener is used to keep track for added and removed components, listen
86   *    PostAddToViewEvent and PreRemoveFromViewEvent event triggered by UIComponent.setParent()
87   *    method.
88   * 6. It is not possible to use javax.faces.component.visit API to traverse the component
89   *    tree during save/restore, because UIData.visitTree traverse all rows and we only need
90   *    to restore state per component (not per row).
91   * 7. It is necessary to preserve the order of the children added/removed between requests.
92   * 8. Added and removed components could be seen as subtrees. This imply that we need to save
93   *    the structure of the added components subtree and remove one component could be remove
94   *    all its children and facets from view inclusive.
95   * 9. It is necessary to save and restore the list of added/removed components between several
96   *    requests.
97   * 10.All components ids removed in any moment of time must be preserved.
98   * 11.Each component must be restored only once.
99   * 11.The order is important for ids added when it is traversed the tree, otherwise the algorithm 
100  *    could change the order in which components will be restored.  
101  * 
102  * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
103  * @version $Revision: 1622093 $ $Date: 2014-09-02 19:03:52 +0000 (Tue, 02 Sep 2014) $
104  * @since 2.0
105  *
106  */
107 public class DefaultFaceletsStateManagementStrategy extends StateManagementStrategy
108 {
109     public static final String CLIENTIDS_ADDED = "oam.CLIENTIDS_ADDED";
110     
111     public static final String CLIENTIDS_REMOVED = "oam.CLIENTIDS_REMOVED";
112     
113     /**
114      * Key used on component attribute map to indicate if a component was added
115      * after build view, so itself and all descendants should not use partial
116      * state saving. There are two possible values:
117      * 
118      * Key not present: The component uses pss.
119      * ComponentState.ADD: The component was added to the view after build view.
120      * ComponentState.REMOVE_ADD: The component was removed/added to the view. Itself and all
121      * descendants should be saved and restored, but we have to unregister/register
122      * from CLIENTIDS_ADDED and CLIENTIDS_REMOVED lists. See ComponentSupport.markComponentToRestoreFully
123      * for details.
124      * ComponentState.ADDED: The component has been added or removed/added, but it has
125      * been already processed.
126      */
127     public  static final String COMPONENT_ADDED_AFTER_BUILD_VIEW = "oam.COMPONENT_ADDED_AFTER_BUILD_VIEW"; 
128     
129     /**
130      * If this param is set to true (by default), when pss algorithm is executed to save state, a visit tree
131      * traversal is done, instead a plain traversal like previous versions (2.0.7/2.1.1 and earlier) of MyFaces Core.
132      * 
133      * This param is just provided to preserve backwards behavior. 
134      * @deprecated JSF 2.2 change enforces tree visiting as the preferred way. Performance tests shows that plain
135      * visit is faster but the difference is negligible.
136      */
137     @Deprecated
138     @JSFWebConfigParam(since="2.0.8, 2.1.2", defaultValue="true", expectedValues="true, false",
139                        group="state", tags="performance", deprecated=true)
140     public static final String SAVE_STATE_WITH_VISIT_TREE_ON_PSS
141             = "org.apache.myfaces.SAVE_STATE_WITH_VISIT_TREE_ON_PSS";
142     
143     /**
144      * Define how duplicate ids are checked when ProjectStage is Production, by default (auto) it only check ids of
145      * components that does not encapsulate markup (like facelets UILeaf).
146      *  
147      * <ul>
148      * <li>true: check all ids, including ids for components that are transient and encapsulate markup.</li>
149      * <li>auto: (default) check ids of components that does not encapsulate markup (like facelets UILeaf). 
150      * Note ids of UILeaf instances are generated by facelets vdl, start with "j_id", are never rendered 
151      * into the response and UILeaf instances are never used as a target for listeners, so in practice 
152      * there is no need to check such ids. This reduce the overhead associated with generate client ids.</li>
153      * <li>false: do not do any check when ProjectStage is Production</li>
154      * </ul>
155      * <p> According to specification, identifiers must be unique within the scope of the nearest ancestor to 
156      * the component that is a naming container.</p>
157      */
158     @JSFWebConfigParam(since="2.0.12, 2.1.6", defaultValue="auto", expectedValues="true, auto, false",
159                        group="state", tags="performance")
160     public static final String CHECK_ID_PRODUCTION_MODE
161             = "org.apache.myfaces.CHECK_ID_PRODUCTION_MODE";
162     
163     private static final String CHECK_ID_PRODUCTION_MODE_DEFAULT = "auto";
164     private static final String CHECK_ID_PRODUCTION_MODE_TRUE = "true";
165     private static final String CHECK_ID_PRODUCTION_MODE_AUTO = "auto";
166     
167     private static final String SKIP_ITERATION_HINT = "javax.faces.visit.SKIP_ITERATION";
168     
169     private static final String SERIALIZED_VIEW_REQUEST_ATTR = 
170         StateManagerImpl.class.getName() + ".SERIALIZED_VIEW";
171     
172     private static final Object[] EMPTY_STATES = new Object[]{null, null};
173     
174     private static final Set<VisitHint> VISIT_HINTS = Collections.unmodifiableSet( 
175             EnumSet.of(VisitHint.SKIP_ITERATION));
176     
177     private static final String UNIQUE_ID_COUNTER_KEY =
178               "oam.view.uniqueIdCounter";
179     
180     private ViewDeclarationLanguageFactory _vdlFactory;
181     
182     private RenderKitFactory _renderKitFactory = null;
183     
184     private VisitContextFactory _visitContextFactory = null;
185     
186     private String _checkIdsProductionMode;
187     
188     private MyfacesConfig _config;
189     
190     private ViewPoolProcessor _viewPoolProcessor;
191     
192     public DefaultFaceletsStateManagementStrategy ()
193     {
194         this(FacesContext.getCurrentInstance());
195     }
196     
197     public DefaultFaceletsStateManagementStrategy (FacesContext context)
198     {
199         _vdlFactory = (ViewDeclarationLanguageFactory)
200                 FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY);
201         _config = MyfacesConfig.getCurrentInstance(context.getExternalContext());
202         _viewPoolProcessor = ViewPoolProcessor.getInstance(context);
203     }
204     
205     @SuppressWarnings("unchecked")
206     @Override
207     public UIViewRoot restoreView (FacesContext context, String viewId, String renderKitId)
208     {
209         ResponseStateManager manager;
210         Object state[];
211         Map<String, Object> states;
212         UIViewRoot view = null;
213  
214         // The value returned here is expected to be false (set by RestoreViewExecutor), but
215         //we don't know if some ViewHandler wrapper could change it, so it is better to save the value.
216         final boolean oldContextEventState = context.isProcessingEvents();
217         // Get previous state from ResponseStateManager.
218         manager = getRenderKitFactory().getRenderKit(context, renderKitId).getResponseStateManager();
219         
220         state = (Object[]) manager.getState(context, viewId);
221         
222         if (state == null)
223         {
224             //No state could be restored, return null causing ViewExpiredException
225             return null;
226         }
227         
228         if (state[1] instanceof Object[])
229         {
230             Object[] fullState = (Object[]) state[1]; 
231             view = (UIViewRoot) internalRestoreTreeStructure((TreeStructComponent)fullState[0]);
232 
233             if (view != null)
234             {
235                 context.setViewRoot (view);
236                 view.processRestoreState(context, fullState[1]);
237                 
238                 // If the view is restored fully, it is necessary to refresh RequestViewContext, otherwise at
239                 // each ajax request new components associated with @ResourceDependency annotation will be added
240                 // to the tree, making the state bigger without real need.
241                 RequestViewContext.getCurrentInstance(context).
242                         refreshRequestViewContext(context, view);
243                 
244                 if (fullState.length == 3 && fullState[2] != null)
245                 {
246                     context.setResourceLibraryContracts((List) UIComponentBase.
247                         restoreAttachedState(context, fullState[2]));
248                 }
249             }
250         }
251         else
252         {
253             // Per the spec: build the view.
254             ViewDeclarationLanguage vdl = _vdlFactory.getViewDeclarationLanguage(viewId);
255             Object faceletViewState = null;
256             try
257             {
258                 ViewMetadata metadata = vdl.getViewMetadata (context, viewId);
259                 
260                 if (metadata != null)
261                 {
262                     view = metadata.createMetadataView(context);
263                     
264                     // If no view and response complete there is no need to continue
265                     if (view == null && context.getResponseComplete())
266                     {
267                         return null;
268                     }
269                 }
270                 if (view == null)
271                 {
272                     view = context.getApplication().getViewHandler().createView(context, viewId);
273                 }
274                 
275                 context.setViewRoot (view); 
276                 boolean skipBuildView = false;
277                 if (state != null && state[1] != null)
278                 {
279                     // Since JSF 2.2, UIViewRoot.restoreViewScopeState() must be called, but
280                     // to get the state of the root, it is necessary to force calculate the
281                     // id from this location. Remember in this point, PSS is enabled, so the
282                     // code match with the assigment done in 
283                     // FaceletViewDeclarationLanguage.buildView()
284                     states = (Map<String, Object>) state[1];
285                     faceletViewState = UIComponentBase.restoreAttachedState(
286                             context,states.get(ComponentSupport.FACELET_STATE_INSTANCE));
287                     if (faceletViewState != null && _viewPoolProcessor != null)
288                     {
289                         ViewPool viewPool = _viewPoolProcessor.getViewPool(context, view);
290                         if (viewPool != null)
291                         {
292                             ViewStructureMetadata viewMetadata = viewPool.retrieveDynamicViewStructureMetadata(
293                                 context, view, (FaceletState) faceletViewState);
294                             if (viewMetadata != null)
295                             {
296                                 ViewEntry entry = viewPool.popDynamicStructureView(context, view,
297                                         (FaceletState) faceletViewState);
298                                 if (entry != null)
299                                 {
300                                     skipBuildView = true;
301                                     _viewPoolProcessor.cloneAndRestoreView(context, view, entry, viewMetadata);
302                                 }
303                             }
304                         }
305                     }
306                     if (view.getId() == null)
307                     {
308                         view.setId(view.createUniqueId(context, null));
309                     }
310                     if (faceletViewState != null)
311                     {
312                         view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE,  faceletViewState);
313                     }
314                     if (state.length == 3)
315                     {
316                         //Jump to where the count is
317                         view.getAttributes().put(UNIQUE_ID_COUNTER_KEY, state[2]);
318                     }
319                     Object viewRootState = states.get(view.getClientId(context));
320                     if (viewRootState != null)
321                     {
322                         try
323                         {
324                             view.pushComponentToEL(context, view);
325                             view.restoreViewScopeState(context, viewRootState);
326                         }
327                         finally
328                         {
329                             view.popComponentFromEL(context);
330                         }
331                     }
332                 }
333                 // On RestoreViewExecutor, setProcessingEvents is called first to false
334                 // and then to true when postback. Since we need listeners registered to PostAddToViewEvent
335                 // event to be handled, we should enable it again. For partial state saving we need this listeners
336                 // be called from here and relocate components properly.
337                 if (!skipBuildView)
338                 {
339                     try 
340                     {
341                         context.setProcessingEvents (true);
342                         vdl.buildView (context, view);
343                         // In the latest code related to PostAddToView, it is
344                         // triggered no matter if it is applied on postback. It seems that MYFACES-2389, 
345                         // TRINIDAD-1670 and TRINIDAD-1671 are related.
346                         suscribeListeners(view);
347                     }
348                     finally
349                     {
350                         context.setProcessingEvents (oldContextEventState);
351                     }
352                 }
353             }
354             catch (Throwable e)
355             {
356                 throw new FacesException ("unable to create view \"" + viewId + "\"", e);
357             }
358 
359             // Stateless mode only for transient views and non stateless mode for
360             // stateful views. This check avoid apply state over a stateless view.
361             boolean statelessMode = manager.isStateless(context, viewId);
362             if (statelessMode && !view.isTransient())
363             {
364                 throw new IllegalStateException("View is not transient");
365             }
366             if (!statelessMode && view.isTransient())
367             {
368                 throw new IllegalStateException("Cannot apply state over stateless view");
369             }
370             
371             if (state != null && state[1] != null)
372             {
373                 states = (Map<String, Object>) state[1];
374                 //Save the last unique id counter key in UIViewRoot
375                 Integer lastUniqueIdCounter = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY);
376                 
377                 // Visit the children and restore their state.
378                 boolean emptyState = false;
379                 boolean containsFaceletState = states.containsKey(
380                         ComponentSupport.FACELET_STATE_INSTANCE);
381                 if (states.isEmpty())
382                 {
383                     emptyState = true; 
384                 }
385                 else if (states.size() == 1 && 
386                         containsFaceletState)
387                 {
388                     emptyState = true; 
389                 }
390                 //Restore state of current components
391                 if (!emptyState)
392                 {
393                     // Check if there is only one component state
394                     // and that state is UIViewRoot instance (for example
395                     // when using ViewScope)
396                     if ((states.size() == 1 && !containsFaceletState) || 
397                         (states.size() == 2 && containsFaceletState))
398                     {
399                         Object viewState = states.get(view.getClientId(context));
400                         if (viewState != null)
401                         {
402                             restoreViewRootOnlyFromMap(context,viewState, view);
403                         }
404                         else
405                         {
406                             //The component is not viewRoot, restore as usual.
407                             restoreStateFromMap(context, states, view);
408                         }
409                     }
410                     else
411                     {
412                         restoreStateFromMap(context, states, view);
413                     }
414                 }
415                 if (faceletViewState != null)
416                 {
417                     view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE,  faceletViewState);
418                 }
419                 if (lastUniqueIdCounter != null)
420                 {
421                     Integer newUniqueIdCounter = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY);
422                     if (newUniqueIdCounter != null && 
423                         lastUniqueIdCounter.intValue() > newUniqueIdCounter.intValue())
424                     {
425                         // The unique counter was restored by a side effect of 
426                         // restoreState() over UIViewRoot with a lower count,
427                         // to avoid a component duplicate id exception we need to fix the count.
428                         view.getAttributes().put(UNIQUE_ID_COUNTER_KEY, lastUniqueIdCounter);
429                     }
430                 }
431                 handleDynamicAddedRemovedComponents(context, view, states);
432             }
433         }
434         return view;
435     }
436     
437     public void handleDynamicAddedRemovedComponents(FacesContext context, UIViewRoot view, Map<String, Object> states)
438     {
439         List<String> clientIdsRemoved = getClientIdsRemoved(view);
440 
441         if (clientIdsRemoved != null)
442         {
443             Set<String> idsRemovedSet = new HashSet<String>(HashMapUtils.calcCapacity(clientIdsRemoved.size()));
444             context.getAttributes().put(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD, Boolean.TRUE);
445             try
446             {
447                 // perf: clientIds are ArrayList: see method registerOnAddRemoveList(String)
448                 for (int i = 0, size = clientIdsRemoved.size(); i < size; i++)
449                 {
450                     String clientId = clientIdsRemoved.get(i);
451                     if (!idsRemovedSet.contains(clientId))
452                     {
453                         RemoveComponentCallback callback = new RemoveComponentCallback();
454                         view.invokeOnComponent(context, clientId, callback);
455                         if (callback.isComponentFound())
456                         {
457                             //Add only if component found
458                             idsRemovedSet.add(clientId);
459                         }
460                     }
461                 }
462                 clientIdsRemoved.clear();
463                 clientIdsRemoved.addAll(idsRemovedSet);
464             }
465             finally
466             {
467                 context.getAttributes().remove(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD);
468             }
469         }
470         List<String> clientIdsAdded = getClientIdsAdded(view);
471         if (clientIdsAdded != null)
472         {
473             Set<String> idsAddedSet = new HashSet<String>(HashMapUtils.calcCapacity(clientIdsAdded.size()));
474             // perf: clientIds are ArrayList: see method setClientsIdsAdded(String)
475             for (int i = 0, size = clientIdsAdded.size(); i < size; i++)
476             {
477                 String clientId = clientIdsAdded.get(i);
478                 if (!idsAddedSet.contains(clientId))
479                 {
480                     final AttachedFullStateWrapper wrapper = (AttachedFullStateWrapper) states.get(clientId);
481                     if (wrapper != null)
482                     {
483                         final Object[] addedState = (Object[]) wrapper.getWrappedStateObject(); 
484                         if (addedState != null)
485                         {
486                             if (addedState.length == 2)
487                             {
488                                 view = (UIViewRoot)
489                                         internalRestoreTreeStructure((TreeStructComponent) addedState[0]);
490                                 view.processRestoreState(context, addedState[1]);
491                                 break;
492                             }
493                             else
494                             {
495                                 final String parentClientId = (String) addedState[0];
496                                 view.invokeOnComponent(context, parentClientId, 
497                                     new AddComponentCallback(addedState));
498                             }
499                         }
500                     }
501                     idsAddedSet.add(clientId);
502                 }
503             }
504             // Reset this list, because it will be calculated later when the view is being saved
505             // in the right order, preventing duplicates (see COMPONENT_ADDED_AFTER_BUILD_VIEW for details).
506             clientIdsAdded.clear();
507             
508             // This call only has sense when components has been added programatically, because if facelets has control
509             // over all components in the component tree, build the initial state and apply the state will have the
510             // same effect.
511             RequestViewContext.getCurrentInstance(context).
512                     refreshRequestViewContext(context, view);
513         }
514     }
515 
516     public static class RemoveComponentCallback implements ContextCallback
517     {
518         private boolean componentFound;
519         
520         public RemoveComponentCallback()
521         {
522             this.componentFound = false;
523         }
524         
525         public void invokeContextCallback(FacesContext context,
526                 UIComponent target)
527         {
528             if (target.getParent() != null && 
529                 !target.getParent().getChildren().remove(target))
530             {
531                 String key = null;
532                 if (target.getParent().getFacetCount() > 0)
533                 {
534                     for (Map.Entry<String, UIComponent> entry :
535                             target.getParent().getFacets().entrySet())
536                     {
537                         if (entry.getValue()==target)
538                         {
539                             key = entry.getKey();
540                             break;
541                         }
542                     }
543                 }
544                 if (key != null)
545                 {
546                     UIComponent removedTarget = target.getParent().getFacets().remove(key);
547                     if (removedTarget != null)
548                     {
549                         this.componentFound = true;
550                     }
551                 }
552             }
553             else
554             {
555                 this.componentFound = true;
556             }
557         }
558         
559         public boolean isComponentFound()
560         {
561             return this.componentFound;
562         }
563     }
564 
565     public static class AddComponentCallback implements ContextCallback
566     {
567         private final Object[] addedState;
568         
569         public AddComponentCallback(Object[] addedState)
570         {
571             this.addedState = addedState;
572         }
573         
574         public void invokeContextCallback(FacesContext context,
575                 UIComponent target)
576         {
577             if (addedState[1] != null)
578             {
579                 String facetName = (String) addedState[1];
580                 UIComponent child
581                         = internalRestoreTreeStructure((TreeStructComponent)
582                                                        addedState[3]);
583                 child.processRestoreState(context, addedState[4]);
584                 target.getFacets().put(facetName,child);
585             }
586             else
587             {
588                 Integer childIndex = (Integer) addedState[2];
589                 UIComponent child
590                         = internalRestoreTreeStructure((TreeStructComponent)
591                                                        addedState[3]);
592                 child.processRestoreState(context, addedState[4]);
593                 
594                 boolean done = false;
595                 // Is the child a facelet controlled component?
596                 if (child.getAttributes().containsKey(ComponentSupport.MARK_CREATED))
597                 {
598                     // By effect of c:forEach it is possible that the component can be duplicated
599                     // in the component tree, so what we need to do as a fallback is replace the
600                     // component in the spot with the restored version.
601                     UIComponent parent = target;
602                     if (parent.getChildCount() > 0)
603                     {
604                         String tagId = (String) child.getAttributes().get(ComponentSupport.MARK_CREATED);
605                         if (childIndex < parent.getChildCount())
606                         {
607                             // Try to find the component quickly 
608                             UIComponent dup = parent.getChildren().get(childIndex);
609                             if (tagId.equals(dup.getAttributes().get(ComponentSupport.MARK_CREATED)))
610                             {
611                                 // Replace
612                                 parent.getChildren().remove(childIndex.intValue());
613                                 parent.getChildren().add(childIndex, child);
614                                 done = true;
615                             }
616                         }
617                         if (!done)
618                         {
619                             // Fallback to iteration
620                             for (int i = 0, childCount = parent.getChildCount(); i < childCount; i ++)
621                             {
622                                 UIComponent dup = parent.getChildren().get(i);
623                                 if (tagId.equals(dup.getAttributes().get(ComponentSupport.MARK_CREATED)))
624                                 {
625                                     // Replace
626                                     parent.getChildren().remove(i);
627                                     parent.getChildren().add(i, child);
628                                     done = true;
629                                     break;
630                                 }
631                             }
632                         }
633                     }
634                 }
635                 if (!done)
636                 {
637                     try
638                     {
639                         target.getChildren().add(childIndex, child);
640                     }
641                     catch (IndexOutOfBoundsException e)
642                     {
643                         // We can't be sure about where should be this 
644                         // item, so just add it. 
645                         target.getChildren().add(child);
646                     }
647                 }
648             }
649         }
650     }
651 
652     @Override
653     public Object saveView (FacesContext context)
654     {
655         UIViewRoot view = context.getViewRoot();
656         Object states;
657         
658         if (view == null)
659         {
660             // Not much that can be done.
661             
662             return null;
663         }
664         
665         Object serializedView = context.getAttributes()
666             .get(SERIALIZED_VIEW_REQUEST_ATTR);
667         
668         //Note on ajax case the method saveState could be called twice: once before start
669         //document rendering and the other one when it is called StateManager.getViewState method.
670         if (serializedView == null)
671         {
672                     
673             // Make sure the client IDs are unique per the spec.
674             
675             if (context.isProjectStage(ProjectStage.Production))
676             {
677                 if (CHECK_ID_PRODUCTION_MODE_AUTO.equals(getCheckIdProductionMode(context)))
678                 {
679                     CheckDuplicateIdFaceletUtils.checkIdsStatefulComponents(context, view);
680                 }
681                 else if (CHECK_ID_PRODUCTION_MODE_TRUE.equals(getCheckIdProductionMode(context)))
682                 {
683                     CheckDuplicateIdFaceletUtils.checkIds(context, view);
684                 }
685             }
686             else
687             {
688                 CheckDuplicateIdFaceletUtils.checkIds(context, view);
689             }
690             
691             // Create save state objects for every component.
692             
693             boolean viewResetable = false;
694             int count = 0;
695             Object faceletViewState = null;
696             boolean saveViewFully = view.getAttributes().containsKey(COMPONENT_ADDED_AFTER_BUILD_VIEW);
697             if (saveViewFully)
698             {
699                 ensureClearInitialState(view);
700                 Object rlcStates = !context.getResourceLibraryContracts().isEmpty() ? 
701                     UIComponentBase.saveAttachedState(context, 
702                                 new ArrayList<String>(context.getResourceLibraryContracts())) : null;
703                 states = new Object[]{
704                             internalBuildTreeStructureToSave(view),
705                             view.processSaveState(context), rlcStates};
706             }
707             else
708             {
709                 states = new HashMap<String, Object>();
710 
711                 faceletViewState = view.getAttributes().get(ComponentSupport.FACELET_STATE_INSTANCE);
712                 if (faceletViewState != null)
713                 {
714                     ((Map<String, Object>)states).put(ComponentSupport.FACELET_STATE_INSTANCE,
715                             UIComponentBase.saveAttachedState(context, faceletViewState));
716                     //Do not save on UIViewRoot
717                     view.getAttributes().remove(ComponentSupport.FACELET_STATE_INSTANCE);
718                 }
719                 if (_viewPoolProcessor != null && 
720                     _viewPoolProcessor.isViewPoolEnabledForThisView(context, view))
721                 {
722                     SaveStateAndResetViewCallback cb = saveStateOnMapVisitTreeAndReset(
723                             context,(Map<String,Object>) states, view,
724                             Boolean.TRUE.equals(
725                         context.getAttributes().get(ViewPoolProcessor.FORCE_HARD_RESET)));
726                     viewResetable = cb.isViewResetable();
727                     count = cb.getCount();
728                 }
729                 else
730                 {
731                     saveStateOnMapVisitTree(context,(Map<String,Object>) states, view);
732                 }
733                 
734                 if ( ((Map<String,Object>)states).isEmpty())
735                 {
736                     states = null;
737                 }
738             }
739             
740             Integer uniqueIdCount = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY);
741             if (uniqueIdCount != null && !uniqueIdCount.equals(1))
742             {
743                 serializedView = new Object[] { null, states, uniqueIdCount };
744             }
745             else if (states == null)
746             {
747                 serializedView = EMPTY_STATES;
748             }
749             else
750             {
751                 serializedView = new Object[] { null, states };
752             }
753             
754             //If view cache enabled store the view state into the pool
755             if (!saveViewFully && _viewPoolProcessor != null)
756             {
757                 if (viewResetable)
758                 {
759                     _viewPoolProcessor.pushResetableView(
760                         context, view, (FaceletState) faceletViewState);
761                 }
762                 else
763                 {
764                     _viewPoolProcessor.pushPartialView(
765                         context, view, (FaceletState) faceletViewState, count);
766                 }
767             }
768             
769             context.getAttributes().put(SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
770 
771         }
772         
773         return serializedView;
774     }
775     
776     private void restoreViewRootOnlyFromMap(
777             final FacesContext context, final Object viewState,
778             final UIComponent view)
779     {
780         // Only viewState found, process it but skip tree
781         // traversal, saving some time.
782         try
783         {
784             //Restore view
785             view.pushComponentToEL(context, view);
786             if (viewState != null && !(viewState instanceof AttachedFullStateWrapper))
787             {
788                 try
789                 {
790                     view.restoreState(context, viewState);
791                 }
792                 catch(Exception e)
793                 {
794                     throw new IllegalStateException(
795                             "Error restoring component: "+
796                             view.getClientId(context), e);
797                 }
798             }
799         }
800         finally
801         {
802              view.popComponentFromEL(context);
803         }
804     }
805     
806     private void restoreStateFromMap(final FacesContext context, final Map<String,Object> states,
807             final UIComponent component)
808     {
809         if (states == null)
810         {
811             return;
812         }
813         
814         try
815         {
816             //Restore view
817             component.pushComponentToEL(context, component);
818             Object state = states.get(component.getClientId(context));
819             if (state != null)
820             {
821                 if (state instanceof AttachedFullStateWrapper)
822                 {
823                     //Don't restore this one! It will be restored when the algorithm remove and add it.
824                     return;
825                 }
826                 try
827                 {
828                     component.restoreState(context, state);
829                 }
830                 catch(Exception e)
831                 {
832                     throw new IllegalStateException("Error restoring component: "+component.getClientId(context), e);
833                 }
834             }
835     
836             //Scan children
837             if (component.getChildCount() > 0)
838             {
839                 //String currentClientId = component.getClientId();
840                 
841                 List<UIComponent> children  = component.getChildren();
842                 for (int i = 0; i < children.size(); i++)
843                 {
844                     UIComponent child = children.get(i);
845                     if (child != null && !child.isTransient())
846                     {
847                         restoreStateFromMap( context, states, child);
848                     }
849                 }
850             }
851     
852             //Scan facets
853             if (component.getFacetCount() > 0)
854             {
855                 Map<String, UIComponent> facetMap = component.getFacets();
856                 
857                 for (Map.Entry<String, UIComponent> entry : facetMap.entrySet())
858                 {
859                     UIComponent child = entry.getValue();
860                     if (child != null && !child.isTransient())
861                     {
862                         //String facetName = entry.getKey();
863                         restoreStateFromMap( context, states, child);
864                     }
865                 }
866             }
867         }
868         finally
869         {
870             component.popComponentFromEL(context);
871         }
872     }
873 
874     static List<String> getClientIdsAdded(UIViewRoot root)
875     {
876         return (List<String>) root.getAttributes().get(CLIENTIDS_ADDED);
877     }
878     
879     static void setClientsIdsAdded(UIViewRoot root, List<String> clientIdsList)
880     {
881         root.getAttributes().put(CLIENTIDS_ADDED, clientIdsList);
882     }
883     
884     static List<String> getClientIdsRemoved(UIViewRoot root)
885     {
886         return (List<String>) root.getAttributes().get(CLIENTIDS_REMOVED);
887     }
888     
889     static void setClientsIdsRemoved(UIViewRoot root, List<String> clientIdsList)
890     {
891         root.getAttributes().put(CLIENTIDS_REMOVED, clientIdsList);
892     }
893     
894     @SuppressWarnings("unchecked")
895     private void registerOnAddRemoveList(FacesContext facesContext, String clientId)
896     {
897         UIViewRoot uiViewRoot = facesContext.getViewRoot();
898 
899         List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot);
900         if (clientIdsAdded == null)
901         {
902             //Create a set that preserve insertion order
903             clientIdsAdded = new ArrayList<String>();
904         }
905         clientIdsAdded.add(clientId);
906 
907         setClientsIdsAdded(uiViewRoot, clientIdsAdded);
908 
909         List<String> clientIdsRemoved = (List<String>) getClientIdsRemoved(uiViewRoot);
910         if (clientIdsRemoved == null)
911         {
912             //Create a set that preserve insertion order
913             clientIdsRemoved = new ArrayList<String>();
914         }
915 
916         clientIdsRemoved.add(clientId);
917 
918         setClientsIdsRemoved(uiViewRoot, clientIdsRemoved);
919     }
920     
921     @SuppressWarnings("unchecked")
922     private void registerOnAddList(FacesContext facesContext, String clientId)
923     {
924         UIViewRoot uiViewRoot = facesContext.getViewRoot();
925 
926         List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot);
927         if (clientIdsAdded == null)
928         {
929             //Create a set that preserve insertion order
930             clientIdsAdded = new ArrayList<String>();
931         }
932         clientIdsAdded.add(clientId);
933 
934         setClientsIdsAdded(uiViewRoot, clientIdsAdded);
935     }
936 
937     private void saveStateOnMapVisitTree(final FacesContext facesContext, final Map<String,Object> states,
938             final UIViewRoot uiViewRoot)
939     {
940         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
941         try
942         {
943             uiViewRoot.visitTree( getVisitContextFactory().getVisitContext(
944                     facesContext, null, VISIT_HINTS), new VisitCallback()
945             {
946                 public VisitResult visit(VisitContext context, UIComponent target)
947                 {
948                     FacesContext facesContext = context.getFacesContext();
949                     Object state;
950                     
951                     if ((target == null) || target.isTransient())
952                     {
953                         // No need to bother with these components or their children.
954                         
955                         return VisitResult.REJECT;
956                     }
957                     
958                     ComponentState componentAddedAfterBuildView
959                             = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW);
960                     
961                     //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used.
962                     if (componentAddedAfterBuildView != null && (target.getParent() != null))
963                     {
964                         if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
965                         {
966                             registerOnAddRemoveList(facesContext, target.getClientId(facesContext));
967                             target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
968                         }
969                         else if (ComponentState.ADD.equals(componentAddedAfterBuildView))
970                         {
971                             registerOnAddList(facesContext, target.getClientId(facesContext));
972                             target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
973                         }
974                         else if (ComponentState.ADDED.equals(componentAddedAfterBuildView))
975                         {
976                             registerOnAddList(facesContext, target.getClientId(facesContext));
977                         }
978                         ensureClearInitialState(target);
979                         //Save all required info to restore the subtree.
980                         //This includes position, structure and state of subtree
981                         
982                         int childIndex = target.getParent().getChildren().indexOf(target);
983                         if (childIndex >= 0)
984                         {
985                             states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( 
986                                     new Object[]{
987                                         target.getParent().getClientId(facesContext),
988                                         null,
989                                         childIndex,
990                                         internalBuildTreeStructureToSave(target),
991                                         target.processSaveState(facesContext)}));
992                         }
993                         else
994                         {
995                             String facetName = null;
996                             if (target.getParent().getFacetCount() > 0)
997                             {
998                                 for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) 
999                                 {
1000                                     if (target.equals(entry.getValue()))
1001                                     {
1002                                         facetName = entry.getKey();
1003                                         break;
1004                                     }
1005                                 }
1006                             }
1007                             states.put(target.getClientId(facesContext),new AttachedFullStateWrapper(new Object[]{
1008                                     target.getParent().getClientId(facesContext),
1009                                     facetName,
1010                                     null,
1011                                     internalBuildTreeStructureToSave(target),
1012                                     target.processSaveState(facesContext)}));
1013                         }
1014                         return VisitResult.REJECT;
1015                     }
1016                     else if (target.getParent() != null)
1017                     {
1018                         state = target.saveState (facesContext);
1019                         
1020                         if (state != null)
1021                         {
1022                             // Save by client ID into our map.
1023                             
1024                             states.put (target.getClientId (facesContext), state);
1025                         }
1026                         
1027                         return VisitResult.ACCEPT;
1028                     }
1029                     else
1030                     {
1031                         //Only UIViewRoot has no parent in a component tree.
1032                         return VisitResult.ACCEPT;
1033                     }
1034                 }
1035             });
1036         }
1037         finally
1038         {
1039             facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
1040         }
1041         if (!uiViewRoot.isTransient())
1042         {
1043             Object state = uiViewRoot.saveState (facesContext);
1044             if (state != null)
1045             {
1046                 // Save by client ID into our map.
1047 
1048                 states.put (uiViewRoot.getClientId (facesContext), state);
1049             }
1050         }
1051     }
1052     
1053     
1054     private SaveStateAndResetViewCallback saveStateOnMapVisitTreeAndReset(final FacesContext facesContext,
1055             final Map<String,Object> states, final UIViewRoot uiViewRoot, boolean forceHardReset)
1056     {
1057         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
1058         SaveStateAndResetViewCallback callback = new SaveStateAndResetViewCallback(
1059                 facesContext.getViewRoot(), states, forceHardReset);
1060         if (forceHardReset)
1061         {
1062             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1063                     ViewPoolProcessor.RESET_MODE_HARD);
1064         }
1065         else
1066         {
1067             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1068                     ViewPoolProcessor.RESET_MODE_SOFT);
1069         }
1070         try
1071         {
1072             if (_viewPoolProcessor != null && 
1073                 !_viewPoolProcessor.isViewPoolEnabledForThisView(facesContext, uiViewRoot))
1074             {
1075                 callback.setViewResetable(false);
1076             }
1077             
1078             // Check if the view has removed components. If that so, it
1079             // means there is some manipulation over the component tree that
1080             // can be rollback, so it is ok to set the view as resetable.
1081             if (callback.isViewResetable())
1082             {
1083                 List<String> removedIds = getClientIdsRemoved(uiViewRoot);
1084                 if (removedIds != null && !removedIds.isEmpty())
1085                 {
1086                     callback.setViewResetable(false);
1087                 }
1088             }
1089 
1090             try
1091             {
1092                 uiViewRoot.visitTree( getVisitContextFactory().getVisitContext(
1093                         facesContext, null, VISIT_HINTS), callback);
1094             }
1095             finally
1096             {
1097                 facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
1098             }
1099             
1100             if (callback.isViewResetable() && callback.isRemoveAddedComponents())
1101             {
1102                 List<String> clientIdsToRemove = getClientIdsAdded(uiViewRoot);
1103 
1104                 if (clientIdsToRemove != null)
1105                 {
1106                     // perf: clientIds are ArrayList: see method registerOnAddRemoveList(String)
1107                     for (int i = 0, size = clientIdsToRemove.size(); i < size; i++)
1108                     {
1109                         String clientId = clientIdsToRemove.get(i);
1110                         uiViewRoot.invokeOnComponent(facesContext, clientId, new RemoveComponentCallback());
1111                     }
1112                 }
1113             }
1114 
1115             Object state = uiViewRoot.saveState (facesContext);
1116             if (state != null)
1117             {
1118                 // Save by client ID into our map.
1119                 states.put (uiViewRoot.getClientId (facesContext), state);
1120 
1121                 //Hard reset (or reset and check state again)
1122                 Integer oldResetMode = (Integer) uiViewRoot.getAttributes().put(
1123                         ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, ViewPoolProcessor.RESET_MODE_HARD);
1124                 state = uiViewRoot.saveState (facesContext);
1125                 uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, oldResetMode);
1126                 if (state != null)
1127                 {
1128                     callback.setViewResetable(false);
1129                 }
1130             }
1131         }
1132         finally
1133         {
1134             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1135                     ViewPoolProcessor.RESET_MODE_OFF);
1136         }
1137         return callback;
1138     }
1139     
1140     private class SaveStateAndResetViewCallback implements VisitCallback
1141     {
1142         private final Map<String, Object> states;
1143         
1144         private final UIViewRoot view;
1145         
1146         private boolean viewResetable;
1147         
1148         private boolean skipRoot;
1149         
1150         private int count;
1151         
1152         private boolean forceHardReset;
1153         
1154         private boolean removeAddedComponents;
1155         
1156         public SaveStateAndResetViewCallback(UIViewRoot view, Map<String, Object> states,
1157                 boolean forceHardReset)
1158         {
1159             this.states = states;
1160             this.view = view;
1161             this.viewResetable = true;
1162             this.skipRoot = true;
1163             this.count = 0;
1164             this.forceHardReset = forceHardReset;
1165             this.removeAddedComponents = false;
1166         }
1167         
1168         public VisitResult visit(VisitContext context, UIComponent target)
1169         {
1170             FacesContext facesContext = context.getFacesContext();
1171             Object state;
1172             this.count++;
1173 
1174             if ((target == null) || target.isTransient())
1175             {
1176                 // No need to bother with these components or their children.
1177 
1178                 return VisitResult.REJECT;
1179             }
1180             
1181             if (skipRoot && target instanceof UIViewRoot)
1182             {
1183                 //UIViewRoot should be scanned at last.
1184                 skipRoot = false;
1185                 return VisitResult.ACCEPT;
1186             }
1187 
1188             ComponentState componentAddedAfterBuildView
1189                     = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW);
1190 
1191             //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used.
1192             if (componentAddedAfterBuildView != null && (target.getParent() != null))
1193             {
1194                 //Set this view as not resetable.
1195                 //setViewResetable(false);
1196                 // Enable flag to remove added components later
1197                 setRemoveAddedComponents(true);
1198                 if (forceHardReset)
1199                 {
1200                     // The ideal is remove the added component here but visitTree does not support that
1201                     // kind of tree manipulation.
1202                     if (isViewResetable() &&
1203                         ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
1204                     {
1205                         setViewResetable(false);
1206                     }
1207                     // it is not important to save anything, skip
1208                     return VisitResult.REJECT;
1209                 }
1210                 if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
1211                 {
1212                     //If the view has removed components, set the view as non resetable
1213                     setViewResetable(false);
1214                     registerOnAddRemoveList(facesContext, target.getClientId(facesContext));
1215                     target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
1216                 }
1217                 else if (ComponentState.ADD.equals(componentAddedAfterBuildView))
1218                 {
1219                     registerOnAddList(facesContext, target.getClientId(facesContext));
1220                     target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
1221                 }
1222                 else if (ComponentState.ADDED.equals(componentAddedAfterBuildView))
1223                 {
1224                     // Later on the check of removed components we'll see if the view
1225                     // is resetable or not.
1226                     registerOnAddList(facesContext, target.getClientId(facesContext));
1227                 }
1228                 ensureClearInitialState(target);
1229                 //Save all required info to restore the subtree.
1230                 //This includes position, structure and state of subtree
1231 
1232                 int childIndex = target.getParent().getChildren().indexOf(target);
1233                 if (childIndex >= 0)
1234                 {
1235                     states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( 
1236                             new Object[]{
1237                                 target.getParent().getClientId(facesContext),
1238                                 null,
1239                                 childIndex,
1240                                 internalBuildTreeStructureToSave(target),
1241                                 target.processSaveState(facesContext)}));
1242                 }
1243                 else
1244                 {
1245                     String facetName = null;
1246                     if (target.getParent().getFacetCount() > 0)
1247                     {
1248                         for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) 
1249                         {
1250                             if (target.equals(entry.getValue()))
1251                             {
1252                                 facetName = entry.getKey();
1253                                 break;
1254                             }
1255                         }
1256                     }
1257                     states.put(target.getClientId(facesContext),new AttachedFullStateWrapper(new Object[]{
1258                             target.getParent().getClientId(facesContext),
1259                             facetName,
1260                             null,
1261                             internalBuildTreeStructureToSave(target),
1262                             target.processSaveState(facesContext)}));
1263                 }
1264                 return VisitResult.REJECT;
1265             }
1266             else if (target.getParent() != null)
1267             {
1268                 if (forceHardReset)
1269                 {
1270                     // force hard reset set reset move on top
1271                     state = target.saveState (facesContext);
1272                     if (state != null)
1273                     {
1274                         setViewResetable(false);
1275                         return VisitResult.REJECT;
1276                     }
1277                 }
1278                 else
1279                 {
1280                     state = target.saveState (facesContext);
1281 
1282                     if (state != null)
1283                     {
1284                         // Save by client ID into our map.
1285                         states.put (target.getClientId (facesContext), state);
1286 
1287                         if (isViewResetable())
1288                         {
1289                             //Hard reset (or reset and check state again)
1290                             Integer oldResetMode = (Integer) view.getAttributes().put(
1291                                     ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1292                                     ViewPoolProcessor.RESET_MODE_HARD);
1293                             state = target.saveState (facesContext);
1294                             view.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1295                                     oldResetMode);
1296                             if (state != null)
1297                             {
1298                                 setViewResetable(false);
1299                             }
1300                         }
1301                     }
1302                 }
1303 
1304                 return VisitResult.ACCEPT;
1305             }
1306             else
1307             {
1308                 //Only UIViewRoot has no parent in a component tree.
1309                 return VisitResult.ACCEPT;
1310             }
1311         }
1312         
1313         /**
1314          * @return the viewResetable
1315          */
1316         public boolean isViewResetable()
1317         {
1318             return viewResetable;
1319         }
1320 
1321         /**
1322          * @param viewResetable the viewResetable to set
1323          */
1324         public void setViewResetable(boolean viewResetable)
1325         {
1326             this.viewResetable = viewResetable;
1327         }
1328         
1329         public int getCount()
1330         {
1331             return count;
1332         }
1333 
1334         /**
1335          * @return the removeAddedComponents
1336          */
1337         public boolean isRemoveAddedComponents()
1338         {
1339             return removeAddedComponents;
1340         }
1341 
1342         /**
1343          * @param removeAddedComponents the removeAddedComponents to set
1344          */
1345         public void setRemoveAddedComponents(boolean removeAddedComponents)
1346         {
1347             this.removeAddedComponents = removeAddedComponents;
1348         }
1349     }
1350     
1351     protected void ensureClearInitialState(UIComponent c)
1352     {
1353         c.clearInitialState();
1354         if (c.getChildCount() > 0)
1355         {
1356             for (int i = 0, childCount = c.getChildCount(); i < childCount; i++)
1357             {
1358                 UIComponent child = c.getChildren().get(i);
1359                 ensureClearInitialState(child);
1360             }
1361         }
1362         if (c.getFacetCount() > 0)
1363         {
1364             for (UIComponent child : c.getFacets().values())
1365             {
1366                 ensureClearInitialState(child);
1367             }
1368         }
1369     }
1370     
1371     public void suscribeListeners(UIViewRoot uiViewRoot)
1372     {
1373         boolean listenerSubscribed = false;
1374         List<SystemEventListener> pavList = uiViewRoot.getViewListenersForEventClass(PostAddToViewEvent.class);
1375         if (pavList != null)
1376         {
1377             for (SystemEventListener listener : pavList)
1378             {
1379                 if (listener instanceof PostAddPreRemoveFromViewListener)
1380                 {
1381                     listenerSubscribed = true;
1382                     break;
1383                 }
1384             }
1385         }
1386         if (!listenerSubscribed)
1387         {
1388             PostAddPreRemoveFromViewListener componentListener = new PostAddPreRemoveFromViewListener();
1389             uiViewRoot.subscribeToViewEvent(PostAddToViewEvent.class, componentListener);
1390             uiViewRoot.subscribeToViewEvent(PreRemoveFromViewEvent.class, componentListener);
1391         }
1392     }
1393     
1394     protected RenderKitFactory getRenderKitFactory()
1395     {
1396         if (_renderKitFactory == null)
1397         {
1398             _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
1399         }
1400         return _renderKitFactory;
1401     }
1402     
1403     protected VisitContextFactory getVisitContextFactory()
1404     {
1405         if (_visitContextFactory == null)
1406         {
1407             _visitContextFactory = (VisitContextFactory)FactoryFinder.getFactory(FactoryFinder.VISIT_CONTEXT_FACTORY);
1408         }
1409         return _visitContextFactory;
1410     }
1411 
1412     protected String getCheckIdProductionMode(FacesContext facesContext)
1413     {
1414         if (_checkIdsProductionMode == null)
1415         {
1416             _checkIdsProductionMode
1417                     = WebConfigParamUtils.getStringInitParameter(facesContext.getExternalContext(),
1418                     CHECK_ID_PRODUCTION_MODE, CHECK_ID_PRODUCTION_MODE_DEFAULT); //default (auto)
1419         }
1420         return _checkIdsProductionMode;
1421     }
1422 
1423     
1424     public static class PostAddPreRemoveFromViewListener implements SystemEventListener
1425     {
1426         private transient FacesContext _facesContext;
1427         
1428         private transient Boolean _isRefreshOnTransientBuildPreserveState;
1429 
1430         public boolean isListenerForSource(Object source)
1431         {
1432             // PostAddToViewEvent and PreRemoveFromViewEvent are
1433             // called from UIComponentBase.setParent
1434             return (source instanceof UIComponent);
1435         }
1436         
1437         private boolean isRefreshOnTransientBuildPreserveState()
1438         {
1439             if (_isRefreshOnTransientBuildPreserveState == null)
1440             {
1441                 _isRefreshOnTransientBuildPreserveState = MyfacesConfig.getCurrentInstance(
1442                         _facesContext.getExternalContext()).isRefreshTransientBuildOnPSSPreserveState();
1443             }
1444             return _isRefreshOnTransientBuildPreserveState;
1445         }
1446 
1447         public void processEvent(SystemEvent event)
1448         {
1449             UIComponent component = (UIComponent) event.getSource();
1450             
1451             if (component.isTransient())
1452             {
1453                 return;
1454             }
1455             
1456             // This is a view listener. It is not saved on the state and this listener
1457             // is suscribed each time the view is restored, so we can cache facesContext
1458             // here
1459             if (_facesContext == null)
1460             {
1461                 _facesContext = FacesContext.getCurrentInstance();
1462             }
1463             
1464             if (event instanceof PostAddToViewEvent)
1465             {
1466                 if (!isRefreshOnTransientBuildPreserveState() &&
1467                     Boolean.TRUE.equals(_facesContext.getAttributes().get(StateManager.IS_BUILDING_INITIAL_STATE)))
1468                 {
1469                     return;
1470                 }
1471 
1472                 //PostAddToViewEvent
1473                 component.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADD);
1474             }
1475             else
1476             {
1477                 //FacesContext facesContext = FacesContext.getCurrentInstance();
1478                 // In this case if we are removing components on build, it is not necessary to register
1479                 // again the current id, and its more, it could cause a concurrent exception. But note
1480                 // we need to propagate PreRemoveFromViewEvent, otherwise the view will not be restored
1481                 // correctly.
1482                 if (FaceletViewDeclarationLanguage.isRemovingComponentBuild(_facesContext))
1483                 {
1484                     return;
1485                 }
1486 
1487                 if (!isRefreshOnTransientBuildPreserveState() &&
1488                     FaceletCompositionContext.getCurrentInstance(_facesContext) != null &&
1489                     (component.getAttributes().containsKey(ComponentSupport.MARK_CREATED) ||
1490                      component.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER))
1491                     )
1492                 {
1493                     // Components removed by facelets algorithm does not need to be registered
1494                     // unless preserve state mode is used, because PSS initial state is changed
1495                     // to restore delta properly.
1496                     // MYFACES-3554 It is possible to find use cases where a component
1497                     // created by a facelet tag is changed dynamically in some way in render
1498                     // response time, so we need to check here also when facelets algorithm
1499                     // is running or not. 
1500                     return;
1501                 }
1502                 
1503                 //PreRemoveFromViewEvent
1504                 UIViewRoot uiViewRoot = _facesContext.getViewRoot();
1505                 
1506                 List<String> clientIdsRemoved = getClientIdsRemoved(uiViewRoot);
1507                 if (clientIdsRemoved == null)
1508                 {
1509                     //Create a set that preserve insertion order
1510                     clientIdsRemoved = new ArrayList<String>();
1511                 }
1512                 clientIdsRemoved.add(component.getClientId(_facesContext));
1513                 setClientsIdsRemoved(uiViewRoot, clientIdsRemoved);
1514             }
1515         }
1516     }
1517     
1518     private static TreeStructComponent internalBuildTreeStructureToSave(UIComponent component)
1519     {
1520         TreeStructComponent structComp = new TreeStructComponent(component.getClass().getName(),
1521                                                                  component.getId());
1522 
1523         //children
1524         if (component.getChildCount() > 0)
1525         {
1526             List<TreeStructComponent> structChildList = new ArrayList<TreeStructComponent>();
1527             for (int i = 0, childCount = component.getChildCount(); i < childCount; i++)
1528             {
1529                 UIComponent child = component.getChildren().get(i);     
1530                 if (!child.isTransient())
1531                 {
1532                     TreeStructComponent structChild = internalBuildTreeStructureToSave(child);
1533                     structChildList.add(structChild);
1534                 }
1535             }
1536             
1537             TreeStructComponent[] childArray = structChildList.toArray(new TreeStructComponent[structChildList.size()]);
1538             structComp.setChildren(childArray);
1539         }
1540 
1541         //facets
1542         
1543         if (component.getFacetCount() > 0)
1544         {
1545             Map<String, UIComponent> facetMap = component.getFacets();
1546             List<Object[]> structFacetList = new ArrayList<Object[]>();
1547             for (Map.Entry<String, UIComponent> entry : facetMap.entrySet())
1548             {
1549                 UIComponent child = entry.getValue();
1550                 if (!child.isTransient())
1551                 {
1552                     String facetName = entry.getKey();
1553                     TreeStructComponent structChild = internalBuildTreeStructureToSave(child);
1554                     structFacetList.add(new Object[] {facetName, structChild});
1555                 }
1556             }
1557             
1558             Object[] facetArray = structFacetList.toArray(new Object[structFacetList.size()]);
1559             structComp.setFacets(facetArray);
1560         }
1561 
1562         return structComp;
1563     }
1564     
1565     private static UIComponent internalRestoreTreeStructure(TreeStructComponent treeStructComp)
1566     {
1567         String compClass = treeStructComp.getComponentClass();
1568         String compId = treeStructComp.getComponentId();
1569         UIComponent component = (UIComponent)ClassUtils.newInstance(compClass);
1570         component.setId(compId);
1571 
1572         //children
1573         TreeStructComponent[] childArray = treeStructComp.getChildren();
1574         if (childArray != null)
1575         {
1576             List<UIComponent> childList = component.getChildren();
1577             for (int i = 0, len = childArray.length; i < len; i++)
1578             {
1579                 UIComponent child = internalRestoreTreeStructure(childArray[i]);
1580                 childList.add(child);
1581             }
1582         }
1583 
1584         //facets
1585         Object[] facetArray = treeStructComp.getFacets();
1586         if (facetArray != null)
1587         {
1588             Map<String, UIComponent> facetMap = component.getFacets();
1589             for (int i = 0, len = facetArray.length; i < len; i++)
1590             {
1591                 Object[] tuple = (Object[])facetArray[i];
1592                 String facetName = (String)tuple[0];
1593                 TreeStructComponent structChild = (TreeStructComponent)tuple[1];
1594                 UIComponent child = internalRestoreTreeStructure(structChild);
1595                 facetMap.put(facetName, child);
1596             }
1597         }
1598 
1599         return component;
1600     }
1601 
1602     public static class TreeStructComponent implements Serializable
1603     {
1604         private static final long serialVersionUID = 5069109074684737231L;
1605         private String _componentClass;
1606         private String _componentId;
1607         private TreeStructComponent[] _children = null; // Array of children
1608         private Object[] _facets = null; // Array of Array-tuples with Facetname and TreeStructComponent
1609 
1610         TreeStructComponent(String componentClass, String componentId)
1611         {
1612             _componentClass = componentClass;
1613             _componentId = componentId;
1614         }
1615 
1616         public String getComponentClass()
1617         {
1618             return _componentClass;
1619         }
1620 
1621         public String getComponentId()
1622         {
1623             return _componentId;
1624         }
1625 
1626         void setChildren(TreeStructComponent[] children)
1627         {
1628             _children = children;
1629         }
1630 
1631         TreeStructComponent[] getChildren()
1632         {
1633             return _children;
1634         }
1635 
1636         Object[] getFacets()
1637         {
1638             return _facets;
1639         }
1640 
1641         void setFacets(Object[] facets)
1642         {
1643             _facets = facets;
1644         }
1645     }
1646     
1647 }