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.trinidad.change;
20  
21  import java.io.IOException;
22  import java.io.InvalidObjectException;
23  import java.io.Serializable;
24  
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.ListIterator;
31  import java.util.Map;
32  import java.util.Queue;
33  import java.util.Set;
34  import java.util.concurrent.ConcurrentHashMap;
35  import java.util.concurrent.ConcurrentLinkedQueue;
36  import java.util.concurrent.ConcurrentMap;
37  import java.util.concurrent.CopyOnWriteArrayList;
38  
39  import javax.faces.component.NamingContainer;
40  import javax.faces.component.UIComponent;
41  import javax.faces.component.UIViewRoot;
42  import javax.faces.context.ExternalContext;
43  import javax.faces.context.FacesContext;
44  
45  import org.apache.myfaces.trinidad.context.RequestContext;
46  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
47  import org.apache.myfaces.trinidad.util.CollectionUtils;
48  import org.apache.myfaces.trinidad.util.ComponentUtils;
49  
50  import org.apache.myfaces.trinidad.webapp.UIXComponentELTag;
51  
52  import org.w3c.dom.Document;
53  
54  
55  /**
56   * A ChangeManager implementation that manages persisting the added Changes at the session. 
57   * This means the lifetime of Changes added such is within the session scope. If any of the changes
58   * are managed as state changes and restored by JSF state saving mechanism, the SessionChangeManager
59   * will not re-apply such changes. For example: AttributeComponentChanges are not applied during
60   * a postback unless its target component happened to be a result of any move/add operation, this is
61   * because attribute changes are handled by state manager during postback for common cases.
62   * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/change/SessionChangeManager.java#0 $) $Date: 10-nov-2005.19:06:35 $
63   */
64  public class SessionChangeManager extends BaseChangeManager
65  {
66    /**
67     * {@inheritDoc}
68     * @param context The FacesContext instance for the current request.
69     */
70    @Override
71    public void applyComponentChangesForCurrentView(FacesContext context)
72    {
73      _applyComponentChanges(context, null);
74    }
75  
76    /**
77     * {@inheritDoc}
78     * @param context The FacesContext instance for the current request.
79     */
80     @Override
81    public void applyComponentChangesForSubtree(
82      FacesContext    context,
83      NamingContainer root
84      )
85    {
86      String rootId = null;
87      
88      if (root != null)
89      {
90        if (!(root instanceof UIComponent))
91        {
92          throw new IllegalArgumentException(_LOG.getMessage(
93            "INVALID_TYPE", root));
94        }
95        
96        rootId = ComponentUtils.getScopedIdForComponent((UIComponent)root, context.getViewRoot());
97      }
98  
99      _applyComponentChanges(context, rootId);
100   }
101   
102   /**
103    * {@inheritDoc}
104    */
105   @Override
106   public void applySimpleComponentChanges(FacesContext context, UIComponent component)
107   {
108     // we don't need to apply the component changes if we restored the state, since the
109     // attributes will be up-to-date
110     if (!_isStateRestored(context))
111     {
112       String         sessionKey     = _getSessionKey(context);
113       ChangesForView changesForView = _getChangesForView(context, sessionKey, false);
114       
115       if (changesForView != null)
116       {
117         changesForView.applySimpleComponentChanges(context, component);
118       }
119     }
120   }
121 
122   /**
123    * Adds a ComponentChange and registers against the supplied component.
124    * Changes added thus live at Session scope.
125    * Use applyComponentChangesForCurrentView() to apply these changes.
126    * @param context The FacesContext instance for the current request.
127    * @param targetComponent The target component against which this change needs 
128    * to be registered and applied later on.
129    * @param componentChange The ComponentChange to add.
130    */
131   protected void addComponentChangeImpl(
132     FacesContext    context,
133     UIComponent     targetComponent,
134     ComponentChange componentChange)
135   {    
136     // try to collapse AttributeComponentChanges, handling component movement so that
137     // we can collapse any attribute change on the same component
138     if (componentChange instanceof AttributeComponentChange)
139     {
140       _replaceAttributeChange(context,
141                               targetComponent,
142                               (AttributeComponentChange)componentChange,
143                               false); // replace no matter what
144     }
145     else
146     {
147       String         sessionKey     = _getSessionKey(context);
148       ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
149       
150       // get the absolute scopedId for the target component so that we have a unique identifier
151       // to compare
152       String scopedIdForTargetComponent = 
153         ComponentUtils.getScopedIdForComponent(targetComponent, context.getViewRoot());
154       
155       String logicalScopedIdForTargetComponent = 
156         ComponentUtils.getLogicalScopedIdForComponent(targetComponent, context.getViewRoot());
157       
158       _insertComponentChange(changesForView, scopedIdForTargetComponent, logicalScopedIdForTargetComponent, componentChange);
159       
160       // dirty the key in the session for failover
161       context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
162     }
163   }
164   
165   /**
166    * @inheritDoc
167    */
168   @Override
169   public AttributeComponentChange replaceAttributeChangeIfPresent(
170     FacesContext             context,
171     UIComponent              component,
172     AttributeComponentChange attributeComponentChange)
173   {
174     return _replaceAttributeChange(context, component, attributeComponentChange, true);
175   }  
176 
177   /** 
178    * We don't support DocumentChange persistence
179    */
180   @Override
181   protected Document getDocument(FacesContext context)
182   {
183     return null;
184   }
185 
186   /**
187    *
188    * @param context
189    * @param component
190    * @param attributeComponentChange
191    * @param onlyIfPresent If true, we only insert a new changed if we removed an old one
192    * @return the removed AttributeComponentChange, if any
193    */
194   private AttributeComponentChange _replaceAttributeChange(
195     FacesContext             context,
196     UIComponent              component,
197     AttributeComponentChange attributeComponentChange,
198     boolean                  onlyIfPresent)
199   {    
200     // get the absolute scopedId for the target component so that we have a unique identifier
201     // to compare
202     String scopedIdForTargetComponent = ComponentUtils.getScopedIdForComponent(
203                                                                             component,
204                                                                             context.getViewRoot());
205     
206     // check if we have an existing attribute change for the same attribute name, 
207     // if found, remove it
208     String         sessionKey     = _getSessionKey(context);
209     ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
210 
211     AttributeComponentChange replaced = _extractAttributeChange(changesForView, 
212                                                                 scopedIdForTargetComponent, 
213                                                                 attributeComponentChange);
214     
215     // if found, we insert the new change instance
216     if (!onlyIfPresent || (replaced != null))
217     {
218       String logicalScopedIdForTargetComponent = ComponentUtils.getLogicalScopedIdForComponent(
219                                                                               component,
220                                                                               context.getViewRoot());
221       
222       _insertComponentChange(changesForView, scopedIdForTargetComponent, logicalScopedIdForTargetComponent, attributeComponentChange);
223 
224       // dirty the key in the session for failover
225       context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
226     }
227     
228     return replaced;
229   }  
230 
231   /**
232    * Check if we have an existing attribute change for the same attribute name: 
233    * - if not found, return null
234    * - if found, remove and return the old change instance
235    * 
236    * @param changesForView
237    * @param scopedIdForTargetComponent
238    * @param attributeChange
239    * @return the old change instance, null if not found
240    */
241   private AttributeComponentChange _extractAttributeChange(
242     ChangesForView           changesForView,
243     String                   scopedIdForTargetComponent,
244     AttributeComponentChange attributeChange)
245   {
246     AttributeComponentChange extracted = null;
247         
248     String attributeName = attributeChange.getAttributeName();
249 
250     // would really rather use a Deque here and iterate backwards, which would also make
251     // handling the rename changes easier
252     Iterator<QualifiedComponentChange> changes =
253                                           changesForView.getComponentChangesForView().iterator();
254     
255     // list of changes that have renamed the scoped id of this component.  We need to
256     // handle this aliasing when traversing through the changes looking for matches
257     Iterator<MoveChildComponentChange> renameChanges =
258                                      changesForView.getRenameChanges(scopedIdForTargetComponent);
259     
260     // we need to look through the rename list to map from the current names to
261     // the new names
262     MoveChildComponentChange nextRenameChange;
263     String currTargetScopedId;
264     
265     if (renameChanges.hasNext())
266     {
267       // we have at least one rename change, so get it and find the name that this
268       // component was originally known by
269       nextRenameChange = renameChanges.next();
270       currTargetScopedId = nextRenameChange.getSourceScopedId();
271     }
272     else
273     {
274       nextRenameChange = null;
275       currTargetScopedId = scopedIdForTargetComponent;
276     }
277     
278     // loop forward through the changes looking for AttributeChanges to collapse
279     while (changes.hasNext())
280     {
281       QualifiedComponentChange currQualifiedChange = changes.next();
282       
283       if (currQualifiedChange.getComponentChange() == nextRenameChange)
284       {
285         // we got a match, so update the scoped id we should be looking for
286         currTargetScopedId = nextRenameChange.getDestinationScopedId();
287         
288         nextRenameChange = (renameChanges.hasNext())
289                              ? renameChanges.next()
290                              : null;
291       }
292       else if (currQualifiedChange.getTargetComponentScopedId().equals(currTargetScopedId))
293       {
294         // found a change on the same component.  Check if it's an AttributeChange
295         ComponentChange currChange = currQualifiedChange.getComponentChange();
296         
297         if (currChange instanceof AttributeComponentChange)
298         {
299           AttributeComponentChange currAttributeChange = (AttributeComponentChange)currChange;
300           
301           // Check if the AttributeChange is for the same attribute
302           if (attributeName.equals(currAttributeChange.getAttributeName()))
303           {
304             // the old AttributeChange is for the same attribute, so remove it since the
305             // new AttributeChange would eclipse it anyway.
306             changes.remove();
307             extracted = currAttributeChange;
308             break;
309           }
310         }
311       }
312     }
313 
314     return extracted;    
315   }
316 
317   /**
318    * insert a component change for a specific component
319    * 
320    * @param changesForView
321    * @param scopedIdForTargetComponent
322    * @param logicalScopedIdForTargetComponent
323    * @param componentChange
324    */
325   private void _insertComponentChange(ChangesForView changesForView,
326                                       String scopedIdForTargetComponent,
327                                       String logicalScopedIdForTargetComponent,
328                                       ComponentChange componentChange) 
329   { 
330     QualifiedComponentChange newQualifiedChange = 
331       new QualifiedComponentChange(scopedIdForTargetComponent,
332                                    logicalScopedIdForTargetComponent,
333                                    componentChange);
334     
335     changesForView.addChange(newQualifiedChange);
336   }
337 
338   /**
339    * Implementation shared by applyComponentChangesForCurrentView() and
340    * applyComponentChangesForSubtree().
341    * @param context The FacesContext instance for this request.
342    * @param rootId The scoped id of theNamingContainer that contains the 
343    * component subtree to which ComponentChanges should be applied.  If null, 
344    * all changes are applied.
345    */
346   private void _applyComponentChanges(
347     FacesContext   context,
348     String         rootId
349     )
350   {
351     ChangesForView changesForView = _getReadOnlyChangesForView(context);
352     
353     UIViewRoot viewRoot = context.getViewRoot();
354     
355     // retrieve the ComponentChanges for this current viewid    
356     boolean isStateRestored = _isStateRestored(context);
357     
358     // components that have their attribute application forced because a change that would confuse
359     // state saving has been applied
360     Set<String> attributeForcedComponents = new HashSet<String>();
361     
362     // loop through the viewId's changes, applying the changes
363     for (QualifiedComponentChange qualifiedChange : changesForView.getComponentChangesForView())
364     {
365       ComponentChange componentChange = qualifiedChange.getComponentChange();
366       String targetComponentScopedId  = qualifiedChange.getTargetComponentScopedId();
367 
368       // We do not apply attribute changes if it is a postback, because we expect that
369       // 1. Users calling ChangeManager.addComponentChange() would also apply the change right at
370       //  that point in time (maybe by calling ComponentChange.changeComponent() method).
371       // 2. If #1 was done, JSF state manager will consider this a state change and will store and
372       //  restore it during subsequent postbacks, so there is no need for applying attribute changes
373       //  for postback cases. There are few exceptions where the state management will not help, for
374       //  which we force the attribute changes even when it is a postback.
375       if (isStateRestored &&
376           componentChange instanceof AttributeComponentChange &&
377           !attributeForcedComponents.contains(targetComponentScopedId))
378       {
379         continue;
380       }
381       
382       // If change target for the qualified change is not inside of the specified root, skip
383       if (!_acceptChange(qualifiedChange, rootId))
384         continue;
385       
386       UIComponent targetComponent = viewRoot.findComponent(targetComponentScopedId);
387       
388       // Possible that the target component no more exists in the view, if yes, skip
389       if (targetComponent == null)
390       {
391         _LOG.info(this.getClass().getName(),
392                   "applyComponentChangesForCurrentView",
393                   "TARGET_COMPONENT_MISSING_CHANGE_FAILED",
394                   targetComponentScopedId);
395         continue;
396       }
397      
398       // Apply the change
399       componentChange.changeComponent(targetComponent);
400       
401       // Now that the change is applied, we can identify if the components altered by the currently
402       //  applied change needs forced application of any further changes regardless of request 
403       //  being a postback.
404       if (componentChange instanceof MoveChildComponentChange)
405       {
406         String destinationScopedId = 
407                              ((MoveChildComponentChange)componentChange).getDestinationScopedId();
408         
409         // we no longer need to force the old scoped id, if any, but we do need to force the new one
410         attributeForcedComponents.remove(targetComponentScopedId);
411         attributeForcedComponents.add(destinationScopedId);
412       }
413       else if (componentChange instanceof SetFacetChildComponentChange)
414       {
415         String facetName = ((SetFacetChildComponentChange)componentChange).getFacetName();
416         UIComponent facetComponent = targetComponent.getFacet(facetName);
417         
418         if (facetComponent != null)
419         {
420           String facetScopedId = ComponentUtils.getScopedIdForComponent(facetComponent, viewRoot);
421           
422           attributeForcedComponents.add(facetScopedId);
423         }
424       }
425       else if (componentChange instanceof AddChildComponentChange)
426       {
427         // Get the added component from AddComponentChange, this component is actually re-created 
428         //  from the proxy, and not the actual added component. 
429         //  Bit hacky but this is only way to get Id.
430         String addedComponentId = ((AddChildComponentChange)componentChange).getComponent().getId();
431         
432         // Now get the actual added component finding from the parent to which it was added to
433         UIComponent addedComponent = ComponentUtils.findRelativeComponent(targetComponent,
434                                                                           addedComponentId);
435                 
436         if (addedComponent != null)
437         {
438           String addedChildComponentScopedId= ComponentUtils.getScopedIdForComponent(addedComponent,
439                                                                                      viewRoot);
440           attributeForcedComponents.add(addedChildComponentScopedId);
441         }
442       }
443     }  
444   }
445   
446   /**
447    * Is the state restored by JSF state manager in this request. This is usually true if this is a
448    *  postback request. Additionally check if the document tag created a document component, because
449    *  if this is the case, we are sure that there was no state restoration.
450    */
451   private boolean _isStateRestored(FacesContext facesContext)
452   {
453     /*
454      * We will always return false for now. The reason is, if the page has a included fragment,
455      * and the fragment gets replaced during ppr, the changes inside the region will be lost.
456      */
457     return false;
458     //boolean docCompCreated = Boolean.TRUE.equals(facesContext.getExternalContext().
459     //                               getRequestMap().get(UIXComponentELTag.DOCUMENT_CREATED_KEY));
460     //return (docCompCreated) ? false : RequestContext.getCurrentInstance().isPostback();
461   }  
462 
463   /**
464    * Tests whether the specified change should be applied based on the
465    * specified root id.  If root id is null, all changes are accepted/applied.
466    * If the root id is non-null, only changes which target ids underneath
467    * the root id are accepted/applied.
468    * 
469    * @param qualifiedChange the change to test
470    * @param rootId the scoped id of the NamingContainer for which we
471    *   are applying changes
472    * @return true if rootId is null, or if the qualifiedChange targets a
473    *   component underneath the NamingContainer identified by the rootId.
474    */
475   private boolean _acceptChange(
476     QualifiedComponentChange qualifiedChange,
477     String rootId
478     )
479   {
480     if (rootId != null)
481     {
482       String id = qualifiedChange.getTargetComponentScopedId();
483       return (id.startsWith(rootId) && (id.length() != rootId.length()));    
484     }
485     else
486     {
487       return true;
488     }
489   }
490 
491   private ChangesForView _getReadOnlyChangesForView(FacesContext context)
492   {
493     String sessionKey = _getSessionKey(context);
494     
495     return _getChangesForView(context, sessionKey, false);
496   }
497 
498   /**
499    * Gets the in-order list of component changes for the given view.
500    * @param context The FacesContext instance for this request.
501    * @param sessionKey The composite session key based on the viewId 
502    * @param createIfNecessary Indicates whether the underlying datastructures
503    * that store the component changes needs to be created if absent.
504    * @return The ChangesForView object containing information about the changes for the specified
505    * viewId, including in-order list of component changes for the supplied view. This
506    * will be in the same order in which the component changes were added through
507    * calls to <code>addComponentChange()</code>.
508    */
509   private ChangesForView _getChangesForView(
510     FacesContext context,
511     String       sessionKey,
512     boolean      createIfNecessary)
513   {
514     ExternalContext extContext = context.getExternalContext();
515     Map<String, Object> sessionMap = extContext.getSessionMap();
516 
517     Object changes = sessionMap.get(sessionKey);
518     
519     if (changes == null)
520     {
521       if (createIfNecessary)
522       {
523         // make sure we only create this viewId's changes entry once
524         Object session = extContext.getSession(true);
525         
526         synchronized (session)
527         {
528           changes = sessionMap.get(sessionKey);
529           
530           if (changes == null)
531           {
532             ChangesForView changesForView = new ChangesForView(true);
533             
534             sessionMap.put(sessionKey, changesForView);
535             
536             return changesForView;  // return the newly created changes
537           }
538           else
539           {
540             return (ChangesForView)changes;  // another thread created the changes for us          
541           }
542         }
543       }
544       else
545       {
546         return _EMPTY_CHANGES;  // no changes and we aren't allowed to create them
547       }
548     }
549     else
550     {
551       return (ChangesForView)changes;  // we have the changes
552     }
553   }
554   
555   /**
556    * Return the Session key to store the changes for this viewId in.  We store each viewId under
557    * a different key to avoid needing to failover all of the changes whenever the changes for
558    * a particular viewId are modified.
559    * @param context
560    * @return
561    */
562   private String _getSessionKey(FacesContext context)
563   {
564     String viewId = context.getViewRoot().getViewId();
565     
566     StringBuilder builder = new StringBuilder(viewId.length() +
567                                               _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY.length());
568     
569     return builder.append(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY).append(viewId).toString();
570   }
571 
572   /**
573    * Tracks the component changes for a particular view as well as all the movement
574    * changes so that component aliasing can be tracked
575    */
576   private static final class ChangesForView implements Serializable
577   {
578     protected ChangesForView(boolean rw)
579     {      
580       if (rw)
581       {
582         _componentChangesForView = new ConcurrentLinkedQueue<QualifiedComponentChange>();
583         _renameChanges = new CopyOnWriteArrayList<MoveChildComponentChange>();
584       }
585       else
586       {
587         _componentChangesForView = CollectionUtils.emptyQueue();
588         _renameChanges = Collections.emptyList();
589       }
590     }
591 
592     @Override
593     public String toString()
594     {
595       return super.toString() + "[componentChange=" + _componentChangesForView +
596              " renameChanges=" + _renameChanges + "]";
597     }
598 
599     @Override
600     public boolean equals(Object o)
601     {
602       if (o == this)
603         return true;
604       
605       if (!(o instanceof ChangesForView))
606         return false;
607       
608       ChangesForView other = (ChangesForView)o;
609       
610       return _componentChangesForView.equals(other._componentChangesForView) &&
611              _renameChanges.equals(other._renameChanges);
612     }
613     
614     @Override
615     public int hashCode()
616     {
617       return _componentChangesForView.hashCode() + 37 * _renameChanges.hashCode();
618     }
619     
620     /** 
621      * Returns the QualifiedComponentChanges for this viewId
622      */
623     protected Iterable<QualifiedComponentChange> getComponentChangesForView()
624     {
625       return _componentChangesForView;
626     }
627     
628     /** 
629      * Adds a change to the QualifiedComponentChanges for this viewId, handling
630      * MoveChildComponentChanges specially to handle cases where the clientId
631      * of a component changes as a result of a rename operation
632      */
633     protected void addChange(QualifiedComponentChange qualifiedChange)
634     {
635       // make sure that we don't add changes while getAttrChanges() is rebuilding the
636       // per-component changes
637       _componentChangesForView.add(qualifiedChange);
638         
639       ComponentChange componentChange = qualifiedChange.getComponentChange();
640 
641       if (componentChange instanceof AttributeComponentChange)
642       {
643         // update the attribute changes with the current change
644         _updateAttributeChange(_attrChanges, _renameMap, qualifiedChange);
645       }
646       else if (componentChange instanceof MoveChildComponentChange)
647       {
648         // we only need to remove moves that actually changed the absolute scoped id of the
649         // component
650         MoveChildComponentChange moveComponentChange = (MoveChildComponentChange)componentChange;
651         
652         if (!moveComponentChange.getSourceScopedId().equals(moveComponentChange.getDestinationScopedId()))
653         {
654           _renameChanges.add(moveComponentChange);
655           
656           // update the rename map to account for this change
657           
658           _updateRenameMap(_renameMap, moveComponentChange);
659         }
660       }
661     }
662       
663     /**
664      * Returns the Iterator of rename changes that affect the current scoped id in ComponentChange order
665      * @return
666      */
667     protected Iterator<MoveChildComponentChange> getRenameChanges(String targetScopedId)
668     {
669       if (!_renameChanges.isEmpty())
670       {
671         String currTargetScopedId = targetScopedId;
672         List<MoveChildComponentChange> renameChanges = null;
673         
674         // iterate from the back of the List determining the MoveChildComponentChange
675         // that are aliased to this scoped id
676         ListIterator<MoveChildComponentChange> moveChanges =
677                                                 _renameChanges.listIterator(_renameChanges.size());
678         
679         while (moveChanges.hasPrevious())
680         {
681           MoveChildComponentChange currMoveChange = moveChanges.previous();
682           
683           if (currTargetScopedId.equals(currMoveChange.getDestinationScopedId()))
684           {
685             // lazily create the list the first time we need it
686             if (renameChanges == null)
687               renameChanges = new ArrayList<MoveChildComponentChange>();
688             
689             renameChanges.add(currMoveChange);
690             
691             // get the new id to search for
692             currTargetScopedId = currMoveChange.getSourceScopedId();
693           }
694         }
695         
696         if (renameChanges != null)
697         {
698           if (renameChanges.size() > 1)
699           {
700             // reverse the list to match the order that we will see these items when traversing
701             // the changes from the forward direction
702             Collections.reverse(renameChanges);
703           }
704           
705           return renameChanges.iterator();
706         }  
707       }
708       
709       return CollectionUtils.emptyIterator();
710     }
711 
712     /**
713      * Apply the attribute changes for this component
714      * @param context
715      * @param component
716      */
717     protected void applySimpleComponentChanges(FacesContext context, UIComponent component)
718     {
719       // Simple component changes always use logical scoped ids because they are consistent across
720       // all phases including tag execution
721       String scopedId = ComponentUtils.getLogicalScopedIdForComponent(component, context.getViewRoot());
722       
723       ConcurrentMap<String, ComponentChange> componentChanges = _attrChanges.get(scopedId);
724       
725       if (componentChanges != null)
726       {
727         for (ComponentChange change : componentChanges.values())
728         {
729           change.changeComponent(component);
730         }
731       }
732     }
733 
734     private void _updateAttributeChange(
735       ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> attrChanges,
736       ConcurrentMap<String, String>                                 renameMap,
737       QualifiedComponentChange                                      qAttrChange)
738     {
739       // update the current attribute values for the scoped id
740       String currScopedId = qAttrChange.getTargetComponentLogicalScopedId();
741       
742       // apply any move rename
743       String originalScopedId = renameMap.get(currScopedId);
744       
745       // we don't add rename mapping until a move, so if there is no entry, the origina
746       // value is good
747       if (originalScopedId == null)
748         originalScopedId = currScopedId;
749       
750       // get the map for this component, creating one if necessary
751       ConcurrentMap<String, ComponentChange> changesForComponent = 
752                                                            attrChanges.get(originalScopedId);
753       
754       // if we haven't registered a Map yet, create one and register it
755       if (changesForComponent == null)
756       {
757         // =-= bts There probably aren't that many different changes per component.  Maybe
758         //         we need something smaller and more efficient here
759         changesForComponent = new ConcurrentHashMap<String, ComponentChange>();
760         attrChanges.put(originalScopedId, changesForComponent);
761       }
762       
763       AttributeComponentChange attrChange = (AttributeComponentChange)
764                                             qAttrChange.getComponentChange();
765       
766       // update the current AttributeComponentChange for this attribute
767       String attrName = attrChange.getAttributeName();
768       
769       changesForComponent.put(attrName, attrChange);
770       
771     }
772     
773     /**
774      * Update the renamemap with a change
775      * @param renameMap
776      * @param moveChange
777      */
778     private void _updateRenameMap(
779       ConcurrentMap<String, String> renameMap,
780       MoveChildComponentChange      moveChange)
781     {
782       String sourceScopedId      = moveChange.getSourceLogicalScopedId();
783       String destinationScopedId = moveChange.getDestinationLogicalScopedId();
784       
785       // we only need to update the map if we actually changed scoped ids
786       if (!(sourceScopedId.equals(destinationScopedId)))
787       {
788         // remove the old mapping for source
789         String originalScodeId = renameMap.remove(sourceScopedId);
790         
791         // we don't bother adding mappings if there has been no rename, plus there might
792         // not be any attribute changes yet.  In this case, the source scoped id must
793         // be the original id
794         if (originalScodeId == null)
795           originalScodeId = sourceScopedId;
796         
797         // add the new, updated mapping to account for the move
798         renameMap.put(destinationScopedId, originalScodeId);
799       }
800     }
801 
802     private void readObject(java.io.ObjectInputStream in) 
803       throws IOException, ClassNotFoundException 
804     {
805       throw new InvalidObjectException("proxy required");
806     }
807     
808     private Object writeReplace() 
809     {
810       return new SerializationProxy(this);
811     }
812     
813     private static class SerializationProxy implements Serializable 
814     {
815       SerializationProxy(ChangesForView changesForView) 
816       {
817         _componentChangesForView = 
818           new ArrayList<QualifiedComponentChange>(changesForView._componentChangesForView);
819         
820         if (changesForView._renameChanges==Collections.EMPTY_LIST) 
821           _rw = false;
822         else 
823           _rw = true;
824       }
825       
826       private Object readResolve() 
827       {
828         ChangesForView changesForView = new ChangesForView(_rw);
829         for (QualifiedComponentChange qualifiedChange : _componentChangesForView)
830         {
831           changesForView.addChange(qualifiedChange);
832         }
833         
834         return changesForView;
835       }
836       
837       private final List<QualifiedComponentChange> _componentChangesForView;
838       private final boolean _rw;
839       
840       private static final long serialVersionUID = 1L;
841     }
842 
843 
844     private final Queue<QualifiedComponentChange> _componentChangesForView;
845     private final List<MoveChildComponentChange> _renameChanges;
846     
847     // map of original scopedIds to Map of attribute names and their new values.  This allows
848     // us to apply all of attribute changes efficiently
849     private final ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> _attrChanges = 
850       new ConcurrentHashMap<String, ConcurrentMap<String, ComponentChange>>();
851     
852     // map of current scoped ids to original scoped ids.  This enables us to correctly update
853     // the attributes for the original scoped ids even after the component has moved
854     private final ConcurrentMap<String, String> _renameMap = 
855       new ConcurrentHashMap<String, String>();
856 
857     private static final long serialVersionUID = 1L;
858   }
859   
860   private static final ChangesForView _EMPTY_CHANGES = new ChangesForView(false);
861     
862   private static class QualifiedComponentChange implements Serializable
863   {
864     public QualifiedComponentChange(String targetComponentScopedId,
865                                     String targetComponentLogicalScopedId,
866                                     ComponentChange componentChange)
867     {
868       // NO-TRANS : Class is private and inner, no translated message required
869       if (targetComponentScopedId == null || componentChange == null)
870         throw new IllegalArgumentException("Target component scoped id and " +
871                                            "component change is required");
872       
873       _targetComponentScopedId = targetComponentScopedId;
874       _targetComponentLogicalScopedId = (targetComponentScopedId.equals(targetComponentLogicalScopedId)) ? null :
875                                                     targetComponentLogicalScopedId;
876       _componentChange = componentChange;
877     }
878     
879     public String getTargetComponentScopedId()
880     {
881       return _targetComponentScopedId;
882     }
883     
884     public String getTargetComponentLogicalScopedId()
885     {
886       return _targetComponentLogicalScopedId != null ? _targetComponentLogicalScopedId : _targetComponentScopedId;
887     }
888 
889     public ComponentChange getComponentChange()
890     {
891       return _componentChange;
892     }
893     
894     @Override
895     public boolean equals(Object o)
896     {
897       if (o == this)
898         return true;
899       
900       if (!(o instanceof QualifiedComponentChange))
901         return false;
902       
903       QualifiedComponentChange other = (QualifiedComponentChange)o;
904       
905       return getTargetComponentLogicalScopedId().equals(other.getTargetComponentLogicalScopedId()) &&
906              _componentChange.equals(other._componentChange);
907     }
908     
909     @Override
910     public int hashCode()
911     {
912       return getTargetComponentLogicalScopedId().hashCode() + 37 * _componentChange.hashCode();
913     }
914         
915     @Override
916     public String toString()
917     {
918       return super.toString() + "[target=" + _targetComponentScopedId + " logical_target=" + getTargetComponentLogicalScopedId() +
919               " change=" + _componentChange + "]";
920     }
921 
922     private final String _targetComponentScopedId;
923     private final String _targetComponentLogicalScopedId;
924     private final ComponentChange _componentChange;
925 
926     private static final long serialVersionUID = 1L;
927   }
928   
929   private static final String _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY =
930     "org.apache.myfaces.trinidadinternal.ComponentChangesMapForSession";
931       
932   private static final TrinidadLogger _LOG = 
933     TrinidadLogger.createTrinidadLogger(SessionChangeManager.class);
934 }