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