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