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