View Javadoc

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