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: 1570404 $ $Date: 2014-02-20 19:41:33 -0500 (Thu, 20 Feb 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                         view.invokeOnComponent(context, clientId, new RemoveComponentCallback());
454                         idsRemovedSet.add(clientId);
455                     }
456                 }
457                 clientIdsRemoved.clear();
458                 clientIdsRemoved.addAll(idsRemovedSet);
459             }
460             finally
461             {
462                 context.getAttributes().remove(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD);
463             }
464         }
465         List<String> clientIdsAdded = getClientIdsAdded(view);
466         if (clientIdsAdded != null)
467         {
468             Set<String> idsAddedSet = new HashSet<String>(HashMapUtils.calcCapacity(clientIdsAdded.size()));
469             // perf: clientIds are ArrayList: see method setClientsIdsAdded(String)
470             for (int i = 0, size = clientIdsAdded.size(); i < size; i++)
471             {
472                 String clientId = clientIdsAdded.get(i);
473                 if (!idsAddedSet.contains(clientId))
474                 {
475                     final AttachedFullStateWrapper wrapper = (AttachedFullStateWrapper) states.get(clientId);
476                     if (wrapper != null)
477                     {
478                         final Object[] addedState = (Object[]) wrapper.getWrappedStateObject(); 
479                         if (addedState != null)
480                         {
481                             if (addedState.length == 2)
482                             {
483                                 view = (UIViewRoot)
484                                         internalRestoreTreeStructure((TreeStructComponent) addedState[0]);
485                                 view.processRestoreState(context, addedState[1]);
486                                 break;
487                             }
488                             else
489                             {
490                                 final String parentClientId = (String) addedState[0];
491                                 view.invokeOnComponent(context, parentClientId, 
492                                     new AddComponentCallback(addedState));
493                             }
494                         }
495                     }
496                     idsAddedSet.add(clientId);
497                 }
498             }
499             // Reset this list, because it will be calculated later when the view is being saved
500             // in the right order, preventing duplicates (see COMPONENT_ADDED_AFTER_BUILD_VIEW for details).
501             clientIdsAdded.clear();
502             
503             // This call only has sense when components has been added programatically, because if facelets has control
504             // over all components in the component tree, build the initial state and apply the state will have the
505             // same effect.
506             RequestViewContext.getCurrentInstance(context).
507                     refreshRequestViewContext(context, view);
508         }
509     }
510 
511     public static class RemoveComponentCallback implements ContextCallback
512     {
513         public void invokeContextCallback(FacesContext context,
514                 UIComponent target)
515         {
516             if (target.getParent() != null && 
517                 !target.getParent().getChildren().remove(target))
518             {
519                 String key = null;
520                 if (target.getParent().getFacetCount() > 0)
521                 {
522                     for (Map.Entry<String, UIComponent> entry :
523                             target.getParent().getFacets().entrySet())
524                     {
525                         if (entry.getValue()==target)
526                         {
527                             key = entry.getKey();
528                             break;
529                         }
530                     }
531                 }
532                 if (key != null)
533                 {
534                     target.getParent().getFacets().remove(key);
535                 }
536             }
537         }
538     }
539 
540     public static class AddComponentCallback implements ContextCallback
541     {
542         private final Object[] addedState;
543         
544         public AddComponentCallback(Object[] addedState)
545         {
546             this.addedState = addedState;
547         }
548         
549         public void invokeContextCallback(FacesContext context,
550                 UIComponent target)
551         {
552             if (addedState[1] != null)
553             {
554                 String facetName = (String) addedState[1];
555                 UIComponent child
556                         = internalRestoreTreeStructure((TreeStructComponent)
557                                                        addedState[3]);
558                 child.processRestoreState(context, addedState[4]);
559                 target.getFacets().put(facetName,child);
560             }
561             else
562             {
563                 Integer childIndex = (Integer) addedState[2];
564                 UIComponent child
565                         = internalRestoreTreeStructure((TreeStructComponent)
566                                                        addedState[3]);
567                 child.processRestoreState(context, addedState[4]);
568                 try
569                 {
570                     target.getChildren().add(childIndex, child);
571                 }
572                 catch (IndexOutOfBoundsException e)
573                 {
574                     // We can't be sure about where should be this 
575                     // item, so just add it. 
576                     target.getChildren().add(child);
577                 }
578             }
579         }
580     }
581 
582     @Override
583     public Object saveView (FacesContext context)
584     {
585         UIViewRoot view = context.getViewRoot();
586         Object states;
587         
588         if (view == null)
589         {
590             // Not much that can be done.
591             
592             return null;
593         }
594         
595         Object serializedView = context.getAttributes()
596             .get(SERIALIZED_VIEW_REQUEST_ATTR);
597         
598         //Note on ajax case the method saveState could be called twice: once before start
599         //document rendering and the other one when it is called StateManager.getViewState method.
600         if (serializedView == null)
601         {
602                     
603             // Make sure the client IDs are unique per the spec.
604             
605             if (context.isProjectStage(ProjectStage.Production))
606             {
607                 if (CHECK_ID_PRODUCTION_MODE_AUTO.equals(getCheckIdProductionMode(context)))
608                 {
609                     CheckDuplicateIdFaceletUtils.checkIdsStatefulComponents(context, view);
610                 }
611                 else if (CHECK_ID_PRODUCTION_MODE_TRUE.equals(getCheckIdProductionMode(context)))
612                 {
613                     CheckDuplicateIdFaceletUtils.checkIds(context, view);
614                 }
615             }
616             else
617             {
618                 CheckDuplicateIdFaceletUtils.checkIds(context, view);
619             }
620             
621             // Create save state objects for every component.
622             
623             boolean viewResetable = false;
624             int count = 0;
625             Object faceletViewState = null;
626             boolean saveViewFully = view.getAttributes().containsKey(COMPONENT_ADDED_AFTER_BUILD_VIEW);
627             if (saveViewFully)
628             {
629                 ensureClearInitialState(view);
630                 Object rlcStates = !context.getResourceLibraryContracts().isEmpty() ? 
631                     UIComponentBase.saveAttachedState(context, 
632                                 new ArrayList<String>(context.getResourceLibraryContracts())) : null;
633                 states = new Object[]{
634                             internalBuildTreeStructureToSave(view),
635                             view.processSaveState(context), rlcStates};
636             }
637             else
638             {
639                 states = new HashMap<String, Object>();
640 
641                 faceletViewState = view.getAttributes().get(ComponentSupport.FACELET_STATE_INSTANCE);
642                 if (faceletViewState != null)
643                 {
644                     ((Map<String, Object>)states).put(ComponentSupport.FACELET_STATE_INSTANCE,
645                             UIComponentBase.saveAttachedState(context, faceletViewState));
646                     //Do not save on UIViewRoot
647                     view.getAttributes().remove(ComponentSupport.FACELET_STATE_INSTANCE);
648                 }
649                 if (_viewPoolProcessor != null && 
650                     _viewPoolProcessor.isViewPoolEnabledForThisView(context, view))
651                 {
652                     SaveStateAndResetViewCallback cb = saveStateOnMapVisitTreeAndReset(
653                             context,(Map<String,Object>) states, view,
654                             Boolean.TRUE.equals(
655                         context.getAttributes().get(ViewPoolProcessor.FORCE_HARD_RESET)));
656                     viewResetable = cb.isViewResetable();
657                     count = cb.getCount();
658                 }
659                 else
660                 {
661                     saveStateOnMapVisitTree(context,(Map<String,Object>) states, view);
662                 }
663                 
664                 if ( ((Map<String,Object>)states).isEmpty())
665                 {
666                     states = null;
667                 }
668             }
669             
670             Integer uniqueIdCount = (Integer) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY);
671             if (uniqueIdCount != null && !uniqueIdCount.equals(1))
672             {
673                 serializedView = new Object[] { null, states, uniqueIdCount };
674             }
675             else if (states == null)
676             {
677                 serializedView = EMPTY_STATES;
678             }
679             else
680             {
681                 serializedView = new Object[] { null, states };
682             }
683             
684             //If view cache enabled store the view state into the pool
685             if (!saveViewFully && _viewPoolProcessor != null)
686             {
687                 if (viewResetable)
688                 {
689                     _viewPoolProcessor.pushResetableView(
690                         context, view, (FaceletState) faceletViewState);
691                 }
692                 else
693                 {
694                     _viewPoolProcessor.pushPartialView(
695                         context, view, (FaceletState) faceletViewState, count);
696                 }
697             }
698             
699             context.getAttributes().put(SERIALIZED_VIEW_REQUEST_ATTR, serializedView);
700 
701         }
702         
703         return serializedView;
704     }
705     
706     private void restoreViewRootOnlyFromMap(
707             final FacesContext context, final Object viewState,
708             final UIComponent view)
709     {
710         // Only viewState found, process it but skip tree
711         // traversal, saving some time.
712         try
713         {
714             //Restore view
715             view.pushComponentToEL(context, view);
716             if (viewState != null && !(viewState instanceof AttachedFullStateWrapper))
717             {
718                 try
719                 {
720                     view.restoreState(context, viewState);
721                 }
722                 catch(Exception e)
723                 {
724                     throw new IllegalStateException(
725                             "Error restoring component: "+
726                             view.getClientId(context), e);
727                 }
728             }
729         }
730         finally
731         {
732              view.popComponentFromEL(context);
733         }
734     }
735     
736     private void restoreStateFromMap(final FacesContext context, final Map<String,Object> states,
737             final UIComponent component)
738     {
739         if (states == null)
740         {
741             return;
742         }
743         
744         try
745         {
746             //Restore view
747             component.pushComponentToEL(context, component);
748             Object state = states.get(component.getClientId(context));
749             if (state != null)
750             {
751                 if (state instanceof AttachedFullStateWrapper)
752                 {
753                     //Don't restore this one! It will be restored when the algorithm remove and add it.
754                     return;
755                 }
756                 try
757                 {
758                     component.restoreState(context, state);
759                 }
760                 catch(Exception e)
761                 {
762                     throw new IllegalStateException("Error restoring component: "+component.getClientId(context), e);
763                 }
764             }
765     
766             //Scan children
767             if (component.getChildCount() > 0)
768             {
769                 //String currentClientId = component.getClientId();
770                 
771                 List<UIComponent> children  = component.getChildren();
772                 for (int i = 0; i < children.size(); i++)
773                 {
774                     UIComponent child = children.get(i);
775                     if (child != null && !child.isTransient())
776                     {
777                         restoreStateFromMap( context, states, child);
778                     }
779                 }
780             }
781     
782             //Scan facets
783             if (component.getFacetCount() > 0)
784             {
785                 Map<String, UIComponent> facetMap = component.getFacets();
786                 
787                 for (Map.Entry<String, UIComponent> entry : facetMap.entrySet())
788                 {
789                     UIComponent child = entry.getValue();
790                     if (child != null && !child.isTransient())
791                     {
792                         //String facetName = entry.getKey();
793                         restoreStateFromMap( context, states, child);
794                     }
795                 }
796             }
797         }
798         finally
799         {
800             component.popComponentFromEL(context);
801         }
802     }
803 
804     static List<String> getClientIdsAdded(UIViewRoot root)
805     {
806         return (List<String>) root.getAttributes().get(CLIENTIDS_ADDED);
807     }
808     
809     static void setClientsIdsAdded(UIViewRoot root, List<String> clientIdsList)
810     {
811         root.getAttributes().put(CLIENTIDS_ADDED, clientIdsList);
812     }
813     
814     static List<String> getClientIdsRemoved(UIViewRoot root)
815     {
816         return (List<String>) root.getAttributes().get(CLIENTIDS_REMOVED);
817     }
818     
819     static void setClientsIdsRemoved(UIViewRoot root, List<String> clientIdsList)
820     {
821         root.getAttributes().put(CLIENTIDS_REMOVED, clientIdsList);
822     }
823     
824     @SuppressWarnings("unchecked")
825     private void registerOnAddRemoveList(FacesContext facesContext, String clientId)
826     {
827         UIViewRoot uiViewRoot = facesContext.getViewRoot();
828 
829         List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot);
830         if (clientIdsAdded == null)
831         {
832             //Create a set that preserve insertion order
833             clientIdsAdded = new ArrayList<String>();
834         }
835         clientIdsAdded.add(clientId);
836 
837         setClientsIdsAdded(uiViewRoot, clientIdsAdded);
838 
839         List<String> clientIdsRemoved = (List<String>) getClientIdsRemoved(uiViewRoot);
840         if (clientIdsRemoved == null)
841         {
842             //Create a set that preserve insertion order
843             clientIdsRemoved = new ArrayList<String>();
844         }
845 
846         clientIdsRemoved.add(clientId);
847 
848         setClientsIdsRemoved(uiViewRoot, clientIdsRemoved);
849     }
850     
851     @SuppressWarnings("unchecked")
852     private void registerOnAddList(FacesContext facesContext, String clientId)
853     {
854         UIViewRoot uiViewRoot = facesContext.getViewRoot();
855 
856         List<String> clientIdsAdded = (List<String>) getClientIdsAdded(uiViewRoot);
857         if (clientIdsAdded == null)
858         {
859             //Create a set that preserve insertion order
860             clientIdsAdded = new ArrayList<String>();
861         }
862         clientIdsAdded.add(clientId);
863 
864         setClientsIdsAdded(uiViewRoot, clientIdsAdded);
865     }
866 
867     private void saveStateOnMapVisitTree(final FacesContext facesContext, final Map<String,Object> states,
868             final UIViewRoot uiViewRoot)
869     {
870         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
871         try
872         {
873             uiViewRoot.visitTree( getVisitContextFactory().getVisitContext(
874                     facesContext, null, VISIT_HINTS), new VisitCallback()
875             {
876                 public VisitResult visit(VisitContext context, UIComponent target)
877                 {
878                     FacesContext facesContext = context.getFacesContext();
879                     Object state;
880                     
881                     if ((target == null) || target.isTransient())
882                     {
883                         // No need to bother with these components or their children.
884                         
885                         return VisitResult.REJECT;
886                     }
887                     
888                     ComponentState componentAddedAfterBuildView
889                             = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW);
890                     
891                     //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used.
892                     if (componentAddedAfterBuildView != null && (target.getParent() != null))
893                     {
894                         if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
895                         {
896                             registerOnAddRemoveList(facesContext, target.getClientId(facesContext));
897                             target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
898                         }
899                         else if (ComponentState.ADD.equals(componentAddedAfterBuildView))
900                         {
901                             registerOnAddList(facesContext, target.getClientId(facesContext));
902                             target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
903                         }
904                         else if (ComponentState.ADDED.equals(componentAddedAfterBuildView))
905                         {
906                             registerOnAddList(facesContext, target.getClientId(facesContext));
907                         }
908                         ensureClearInitialState(target);
909                         //Save all required info to restore the subtree.
910                         //This includes position, structure and state of subtree
911                         
912                         int childIndex = target.getParent().getChildren().indexOf(target);
913                         if (childIndex >= 0)
914                         {
915                             states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( 
916                                     new Object[]{
917                                         target.getParent().getClientId(facesContext),
918                                         null,
919                                         childIndex,
920                                         internalBuildTreeStructureToSave(target),
921                                         target.processSaveState(facesContext)}));
922                         }
923                         else
924                         {
925                             String facetName = null;
926                             if (target.getParent().getFacetCount() > 0)
927                             {
928                                 for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) 
929                                 {
930                                     if (target.equals(entry.getValue()))
931                                     {
932                                         facetName = entry.getKey();
933                                         break;
934                                     }
935                                 }
936                             }
937                             states.put(target.getClientId(facesContext),new AttachedFullStateWrapper(new Object[]{
938                                     target.getParent().getClientId(facesContext),
939                                     facetName,
940                                     null,
941                                     internalBuildTreeStructureToSave(target),
942                                     target.processSaveState(facesContext)}));
943                         }
944                         return VisitResult.REJECT;
945                     }
946                     else if (target.getParent() != null)
947                     {
948                         state = target.saveState (facesContext);
949                         
950                         if (state != null)
951                         {
952                             // Save by client ID into our map.
953                             
954                             states.put (target.getClientId (facesContext), state);
955                         }
956                         
957                         return VisitResult.ACCEPT;
958                     }
959                     else
960                     {
961                         //Only UIViewRoot has no parent in a component tree.
962                         return VisitResult.ACCEPT;
963                     }
964                 }
965             });
966         }
967         finally
968         {
969             facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
970         }
971         if (!uiViewRoot.isTransient())
972         {
973             Object state = uiViewRoot.saveState (facesContext);
974             if (state != null)
975             {
976                 // Save by client ID into our map.
977 
978                 states.put (uiViewRoot.getClientId (facesContext), state);
979             }
980         }
981     }
982     
983     
984     private SaveStateAndResetViewCallback saveStateOnMapVisitTreeAndReset(final FacesContext facesContext,
985             final Map<String,Object> states, final UIViewRoot uiViewRoot, boolean forceHardReset)
986     {
987         facesContext.getAttributes().put(SKIP_ITERATION_HINT, Boolean.TRUE);
988         SaveStateAndResetViewCallback callback = new SaveStateAndResetViewCallback(
989                 facesContext.getViewRoot(), states, forceHardReset);
990         if (forceHardReset)
991         {
992             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
993                     ViewPoolProcessor.RESET_MODE_HARD);
994         }
995         else
996         {
997             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
998                     ViewPoolProcessor.RESET_MODE_SOFT);
999         }
1000         try
1001         {
1002             if (_viewPoolProcessor != null && 
1003                 !_viewPoolProcessor.isViewPoolEnabledForThisView(facesContext, uiViewRoot))
1004             {
1005                 callback.setViewResetable(false);
1006             }
1007             
1008             // Check if the view has removed components. If that so, it
1009             // means there is some manipulation over the component tree that
1010             // can be rollback, so it is ok to set the view as resetable.
1011             if (callback.isViewResetable())
1012             {
1013                 List<String> removedIds = getClientIdsRemoved(uiViewRoot);
1014                 if (removedIds != null && !removedIds.isEmpty())
1015                 {
1016                     callback.setViewResetable(false);
1017                 }
1018             }
1019 
1020             try
1021             {
1022                 uiViewRoot.visitTree( getVisitContextFactory().getVisitContext(
1023                         facesContext, null, VISIT_HINTS), callback);
1024             }
1025             finally
1026             {
1027                 facesContext.getAttributes().remove(SKIP_ITERATION_HINT);
1028             }
1029             
1030             if (callback.isViewResetable() && callback.isRemoveAddedComponents())
1031             {
1032                 List<String> clientIdsToRemove = getClientIdsAdded(uiViewRoot);
1033 
1034                 if (clientIdsToRemove != null)
1035                 {
1036                     // perf: clientIds are ArrayList: see method registerOnAddRemoveList(String)
1037                     for (int i = 0, size = clientIdsToRemove.size(); i < size; i++)
1038                     {
1039                         String clientId = clientIdsToRemove.get(i);
1040                         uiViewRoot.invokeOnComponent(facesContext, clientId, new RemoveComponentCallback());
1041                     }
1042                 }
1043             }
1044 
1045             Object state = uiViewRoot.saveState (facesContext);
1046             if (state != null)
1047             {
1048                 // Save by client ID into our map.
1049                 states.put (uiViewRoot.getClientId (facesContext), state);
1050 
1051                 //Hard reset (or reset and check state again)
1052                 Integer oldResetMode = (Integer) uiViewRoot.getAttributes().put(
1053                         ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, ViewPoolProcessor.RESET_MODE_HARD);
1054                 state = uiViewRoot.saveState (facesContext);
1055                 uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, oldResetMode);
1056                 if (state != null)
1057                 {
1058                     callback.setViewResetable(false);
1059                 }
1060             }
1061         }
1062         finally
1063         {
1064             uiViewRoot.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1065                     ViewPoolProcessor.RESET_MODE_OFF);
1066         }
1067         return callback;
1068     }
1069     
1070     private class SaveStateAndResetViewCallback implements VisitCallback
1071     {
1072         private final Map<String, Object> states;
1073         
1074         private final UIViewRoot view;
1075         
1076         private boolean viewResetable;
1077         
1078         private boolean skipRoot;
1079         
1080         private int count;
1081         
1082         private boolean forceHardReset;
1083         
1084         private boolean removeAddedComponents;
1085         
1086         public SaveStateAndResetViewCallback(UIViewRoot view, Map<String, Object> states,
1087                 boolean forceHardReset)
1088         {
1089             this.states = states;
1090             this.view = view;
1091             this.viewResetable = true;
1092             this.skipRoot = true;
1093             this.count = 0;
1094             this.forceHardReset = forceHardReset;
1095             this.removeAddedComponents = false;
1096         }
1097         
1098         public VisitResult visit(VisitContext context, UIComponent target)
1099         {
1100             FacesContext facesContext = context.getFacesContext();
1101             Object state;
1102             this.count++;
1103 
1104             if ((target == null) || target.isTransient())
1105             {
1106                 // No need to bother with these components or their children.
1107 
1108                 return VisitResult.REJECT;
1109             }
1110             
1111             if (skipRoot && target instanceof UIViewRoot)
1112             {
1113                 //UIViewRoot should be scanned at last.
1114                 skipRoot = false;
1115                 return VisitResult.ACCEPT;
1116             }
1117 
1118             ComponentState componentAddedAfterBuildView
1119                     = (ComponentState) target.getAttributes().get(COMPONENT_ADDED_AFTER_BUILD_VIEW);
1120 
1121             //Note if UIViewRoot has this marker, JSF 1.2 like state saving is used.
1122             if (componentAddedAfterBuildView != null && (target.getParent() != null))
1123             {
1124                 //Set this view as not resetable.
1125                 //setViewResetable(false);
1126                 // Enable flag to remove added components later
1127                 setRemoveAddedComponents(true);
1128                 if (forceHardReset)
1129                 {
1130                     // The ideal is remove the added component here but visitTree does not support that
1131                     // kind of tree manipulation.
1132                     if (isViewResetable() &&
1133                         ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
1134                     {
1135                         setViewResetable(false);
1136                     }
1137                     // it is not important to save anything, skip
1138                     return VisitResult.REJECT;
1139                 }
1140                 if (ComponentState.REMOVE_ADD.equals(componentAddedAfterBuildView))
1141                 {
1142                     //If the view has removed components, set the view as non resetable
1143                     setViewResetable(false);
1144                     registerOnAddRemoveList(facesContext, target.getClientId(facesContext));
1145                     target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
1146                 }
1147                 else if (ComponentState.ADD.equals(componentAddedAfterBuildView))
1148                 {
1149                     registerOnAddList(facesContext, target.getClientId(facesContext));
1150                     target.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADDED);
1151                 }
1152                 else if (ComponentState.ADDED.equals(componentAddedAfterBuildView))
1153                 {
1154                     // Later on the check of removed components we'll see if the view
1155                     // is resetable or not.
1156                     registerOnAddList(facesContext, target.getClientId(facesContext));
1157                 }
1158                 ensureClearInitialState(target);
1159                 //Save all required info to restore the subtree.
1160                 //This includes position, structure and state of subtree
1161 
1162                 int childIndex = target.getParent().getChildren().indexOf(target);
1163                 if (childIndex >= 0)
1164                 {
1165                     states.put(target.getClientId(facesContext), new AttachedFullStateWrapper( 
1166                             new Object[]{
1167                                 target.getParent().getClientId(facesContext),
1168                                 null,
1169                                 childIndex,
1170                                 internalBuildTreeStructureToSave(target),
1171                                 target.processSaveState(facesContext)}));
1172                 }
1173                 else
1174                 {
1175                     String facetName = null;
1176                     if (target.getParent().getFacetCount() > 0)
1177                     {
1178                         for (Map.Entry<String, UIComponent> entry : target.getParent().getFacets().entrySet()) 
1179                         {
1180                             if (target.equals(entry.getValue()))
1181                             {
1182                                 facetName = entry.getKey();
1183                                 break;
1184                             }
1185                         }
1186                     }
1187                     states.put(target.getClientId(facesContext),new AttachedFullStateWrapper(new Object[]{
1188                             target.getParent().getClientId(facesContext),
1189                             facetName,
1190                             null,
1191                             internalBuildTreeStructureToSave(target),
1192                             target.processSaveState(facesContext)}));
1193                 }
1194                 return VisitResult.REJECT;
1195             }
1196             else if (target.getParent() != null)
1197             {
1198                 if (forceHardReset)
1199                 {
1200                     // force hard reset set reset move on top
1201                     state = target.saveState (facesContext);
1202                     if (state != null)
1203                     {
1204                         setViewResetable(false);
1205                         return VisitResult.REJECT;
1206                     }
1207                 }
1208                 else
1209                 {
1210                     state = target.saveState (facesContext);
1211 
1212                     if (state != null)
1213                     {
1214                         // Save by client ID into our map.
1215                         states.put (target.getClientId (facesContext), state);
1216 
1217                         if (isViewResetable())
1218                         {
1219                             //Hard reset (or reset and check state again)
1220                             Integer oldResetMode = (Integer) view.getAttributes().put(
1221                                     ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1222                                     ViewPoolProcessor.RESET_MODE_HARD);
1223                             state = target.saveState (facesContext);
1224                             view.getAttributes().put(ViewPoolProcessor.RESET_SAVE_STATE_MODE_KEY, 
1225                                     oldResetMode);
1226                             if (state != null)
1227                             {
1228                                 setViewResetable(false);
1229                             }
1230                         }
1231                     }
1232                 }
1233 
1234                 return VisitResult.ACCEPT;
1235             }
1236             else
1237             {
1238                 //Only UIViewRoot has no parent in a component tree.
1239                 return VisitResult.ACCEPT;
1240             }
1241         }
1242         
1243         /**
1244          * @return the viewResetable
1245          */
1246         public boolean isViewResetable()
1247         {
1248             return viewResetable;
1249         }
1250 
1251         /**
1252          * @param viewResetable the viewResetable to set
1253          */
1254         public void setViewResetable(boolean viewResetable)
1255         {
1256             this.viewResetable = viewResetable;
1257         }
1258         
1259         public int getCount()
1260         {
1261             return count;
1262         }
1263 
1264         /**
1265          * @return the removeAddedComponents
1266          */
1267         public boolean isRemoveAddedComponents()
1268         {
1269             return removeAddedComponents;
1270         }
1271 
1272         /**
1273          * @param removeAddedComponents the removeAddedComponents to set
1274          */
1275         public void setRemoveAddedComponents(boolean removeAddedComponents)
1276         {
1277             this.removeAddedComponents = removeAddedComponents;
1278         }
1279     }
1280     
1281     protected void ensureClearInitialState(UIComponent c)
1282     {
1283         c.clearInitialState();
1284         if (c.getChildCount() > 0)
1285         {
1286             for (int i = 0, childCount = c.getChildCount(); i < childCount; i++)
1287             {
1288                 UIComponent child = c.getChildren().get(i);
1289                 ensureClearInitialState(child);
1290             }
1291         }
1292         if (c.getFacetCount() > 0)
1293         {
1294             for (UIComponent child : c.getFacets().values())
1295             {
1296                 ensureClearInitialState(child);
1297             }
1298         }
1299     }
1300     
1301     public void suscribeListeners(UIViewRoot uiViewRoot)
1302     {
1303         PostAddPreRemoveFromViewListener componentListener = new PostAddPreRemoveFromViewListener();
1304         uiViewRoot.subscribeToViewEvent(PostAddToViewEvent.class, componentListener);
1305         uiViewRoot.subscribeToViewEvent(PreRemoveFromViewEvent.class, componentListener);
1306     }
1307     
1308     protected RenderKitFactory getRenderKitFactory()
1309     {
1310         if (_renderKitFactory == null)
1311         {
1312             _renderKitFactory = (RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
1313         }
1314         return _renderKitFactory;
1315     }
1316     
1317     protected VisitContextFactory getVisitContextFactory()
1318     {
1319         if (_visitContextFactory == null)
1320         {
1321             _visitContextFactory = (VisitContextFactory)FactoryFinder.getFactory(FactoryFinder.VISIT_CONTEXT_FACTORY);
1322         }
1323         return _visitContextFactory;
1324     }
1325 
1326     protected String getCheckIdProductionMode(FacesContext facesContext)
1327     {
1328         if (_checkIdsProductionMode == null)
1329         {
1330             _checkIdsProductionMode
1331                     = WebConfigParamUtils.getStringInitParameter(facesContext.getExternalContext(),
1332                     CHECK_ID_PRODUCTION_MODE, CHECK_ID_PRODUCTION_MODE_DEFAULT); //default (auto)
1333         }
1334         return _checkIdsProductionMode;
1335     }
1336 
1337     
1338     public static class PostAddPreRemoveFromViewListener implements SystemEventListener
1339     {
1340         private transient FacesContext _facesContext;
1341         
1342         private transient Boolean _isRefreshOnTransientBuildPreserveState;
1343 
1344         public boolean isListenerForSource(Object source)
1345         {
1346             // PostAddToViewEvent and PreRemoveFromViewEvent are
1347             // called from UIComponentBase.setParent
1348             return (source instanceof UIComponent);
1349         }
1350         
1351         private boolean isRefreshOnTransientBuildPreserveState()
1352         {
1353             if (_isRefreshOnTransientBuildPreserveState == null)
1354             {
1355                 _isRefreshOnTransientBuildPreserveState = MyfacesConfig.getCurrentInstance(
1356                         _facesContext.getExternalContext()).isRefreshTransientBuildOnPSSPreserveState();
1357             }
1358             return _isRefreshOnTransientBuildPreserveState;
1359         }
1360 
1361         public void processEvent(SystemEvent event)
1362         {
1363             UIComponent component = (UIComponent) event.getSource();
1364             
1365             if (component.isTransient())
1366             {
1367                 return;
1368             }
1369             
1370             // This is a view listener. It is not saved on the state and this listener
1371             // is suscribed each time the view is restored, so we can cache facesContext
1372             // here
1373             if (_facesContext == null)
1374             {
1375                 _facesContext = FacesContext.getCurrentInstance();
1376             }
1377             
1378             if (event instanceof PostAddToViewEvent)
1379             {
1380                 if (!isRefreshOnTransientBuildPreserveState() &&
1381                     Boolean.TRUE.equals(_facesContext.getAttributes().get(StateManager.IS_BUILDING_INITIAL_STATE)))
1382                 {
1383                     return;
1384                 }
1385 
1386                 //PostAddToViewEvent
1387                 component.getAttributes().put(COMPONENT_ADDED_AFTER_BUILD_VIEW, ComponentState.ADD);
1388             }
1389             else
1390             {
1391                 //FacesContext facesContext = FacesContext.getCurrentInstance();
1392                 // In this case if we are removing components on build, it is not necessary to register
1393                 // again the current id, and its more, it could cause a concurrent exception. But note
1394                 // we need to propagate PreRemoveFromViewEvent, otherwise the view will not be restored
1395                 // correctly.
1396                 if (FaceletViewDeclarationLanguage.isRemovingComponentBuild(_facesContext))
1397                 {
1398                     return;
1399                 }
1400 
1401                 if (!isRefreshOnTransientBuildPreserveState() &&
1402                     FaceletCompositionContext.getCurrentInstance(_facesContext) != null &&
1403                     (component.getAttributes().containsKey(ComponentSupport.MARK_CREATED) ||
1404                      component.getAttributes().containsKey(ComponentSupport.COMPONENT_ADDED_BY_HANDLER_MARKER))
1405                     )
1406                 {
1407                     // Components removed by facelets algorithm does not need to be registered
1408                     // unless preserve state mode is used, because PSS initial state is changed
1409                     // to restore delta properly.
1410                     // MYFACES-3554 It is possible to find use cases where a component
1411                     // created by a facelet tag is changed dynamically in some way in render
1412                     // response time, so we need to check here also when facelets algorithm
1413                     // is running or not. 
1414                     return;
1415                 }
1416                 
1417                 //PreRemoveFromViewEvent
1418                 UIViewRoot uiViewRoot = _facesContext.getViewRoot();
1419                 
1420                 List<String> clientIdsRemoved = getClientIdsRemoved(uiViewRoot);
1421                 if (clientIdsRemoved == null)
1422                 {
1423                     //Create a set that preserve insertion order
1424                     clientIdsRemoved = new ArrayList<String>();
1425                 }
1426                 clientIdsRemoved.add(component.getClientId(_facesContext));
1427                 setClientsIdsRemoved(uiViewRoot, clientIdsRemoved);
1428             }
1429         }
1430     }
1431     
1432     private static TreeStructComponent internalBuildTreeStructureToSave(UIComponent component)
1433     {
1434         TreeStructComponent structComp = new TreeStructComponent(component.getClass().getName(),
1435                                                                  component.getId());
1436 
1437         //children
1438         if (component.getChildCount() > 0)
1439         {
1440             List<TreeStructComponent> structChildList = new ArrayList<TreeStructComponent>();
1441             for (int i = 0, childCount = component.getChildCount(); i < childCount; i++)
1442             {
1443                 UIComponent child = component.getChildren().get(i);     
1444                 if (!child.isTransient())
1445                 {
1446                     TreeStructComponent structChild = internalBuildTreeStructureToSave(child);
1447                     structChildList.add(structChild);
1448                 }
1449             }
1450             
1451             TreeStructComponent[] childArray = structChildList.toArray(new TreeStructComponent[structChildList.size()]);
1452             structComp.setChildren(childArray);
1453         }
1454 
1455         //facets
1456         
1457         if (component.getFacetCount() > 0)
1458         {
1459             Map<String, UIComponent> facetMap = component.getFacets();
1460             List<Object[]> structFacetList = new ArrayList<Object[]>();
1461             for (Map.Entry<String, UIComponent> entry : facetMap.entrySet())
1462             {
1463                 UIComponent child = entry.getValue();
1464                 if (!child.isTransient())
1465                 {
1466                     String facetName = entry.getKey();
1467                     TreeStructComponent structChild = internalBuildTreeStructureToSave(child);
1468                     structFacetList.add(new Object[] {facetName, structChild});
1469                 }
1470             }
1471             
1472             Object[] facetArray = structFacetList.toArray(new Object[structFacetList.size()]);
1473             structComp.setFacets(facetArray);
1474         }
1475 
1476         return structComp;
1477     }
1478     
1479     private static UIComponent internalRestoreTreeStructure(TreeStructComponent treeStructComp)
1480     {
1481         String compClass = treeStructComp.getComponentClass();
1482         String compId = treeStructComp.getComponentId();
1483         UIComponent component = (UIComponent)ClassUtils.newInstance(compClass);
1484         component.setId(compId);
1485 
1486         //children
1487         TreeStructComponent[] childArray = treeStructComp.getChildren();
1488         if (childArray != null)
1489         {
1490             List<UIComponent> childList = component.getChildren();
1491             for (int i = 0, len = childArray.length; i < len; i++)
1492             {
1493                 UIComponent child = internalRestoreTreeStructure(childArray[i]);
1494                 childList.add(child);
1495             }
1496         }
1497 
1498         //facets
1499         Object[] facetArray = treeStructComp.getFacets();
1500         if (facetArray != null)
1501         {
1502             Map<String, UIComponent> facetMap = component.getFacets();
1503             for (int i = 0, len = facetArray.length; i < len; i++)
1504             {
1505                 Object[] tuple = (Object[])facetArray[i];
1506                 String facetName = (String)tuple[0];
1507                 TreeStructComponent structChild = (TreeStructComponent)tuple[1];
1508                 UIComponent child = internalRestoreTreeStructure(structChild);
1509                 facetMap.put(facetName, child);
1510             }
1511         }
1512 
1513         return component;
1514     }
1515 
1516     public static class TreeStructComponent implements Serializable
1517     {
1518         private static final long serialVersionUID = 5069109074684737231L;
1519         private String _componentClass;
1520         private String _componentId;
1521         private TreeStructComponent[] _children = null; // Array of children
1522         private Object[] _facets = null; // Array of Array-tuples with Facetname and TreeStructComponent
1523 
1524         TreeStructComponent(String componentClass, String componentId)
1525         {
1526             _componentClass = componentClass;
1527             _componentId = componentId;
1528         }
1529 
1530         public String getComponentClass()
1531         {
1532             return _componentClass;
1533         }
1534 
1535         public String getComponentId()
1536         {
1537             return _componentId;
1538         }
1539 
1540         void setChildren(TreeStructComponent[] children)
1541         {
1542             _children = children;
1543         }
1544 
1545         TreeStructComponent[] getChildren()
1546         {
1547             return _children;
1548         }
1549 
1550         Object[] getFacets()
1551         {
1552             return _facets;
1553         }
1554 
1555         void setFacets(Object[] facets)
1556         {
1557             _facets = facets;
1558         }
1559     }
1560     
1561 }