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