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