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