myfaces-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bsulli...@apache.org
Subject svn commit: r992030 - in /myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad: change/ChangeManager.java change/NullChangeManager.java change/SessionChangeManager.java webapp/UIXComponentELTag.java
Date Thu, 02 Sep 2010 17:41:54 GMT
Author: bsullivan
Date: Thu Sep  2 17:41:53 2010
New Revision: 992030

URL: http://svn.apache.org/viewvc?rev=992030&view=rev
Log:
Trinidad 1899 SessionChangeManager performance and behavior improvements

Modified:
    myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
    myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/NullChangeManager.java
    myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
    myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java

Modified: myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java?rev=992030&r1=992029&r2=992030&view=diff
==============================================================================
--- myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
(original)
+++ myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
Thu Sep  2 17:41:53 2010
@@ -192,6 +192,18 @@ public abstract class ChangeManager
     throw new UnsupportedOperationException("Subclassers must implement");
   }
   
+  /**
+   * Apply non-cross-component changes to a component in its original location.  This is
typically
+   * only called by tags that need to ensure that a newly created component instance is
+   * as up-to-date as possible.
+   * @param context
+   * @param component Component to apply the simple changes to
+   */
+  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
+  {
+    throw new UnsupportedOperationException("Subclassers must implement");    
+  }
+  
   private static class AttributeConverter extends DocumentChangeFactory
   {
     @Override

Modified: myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/NullChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/NullChangeManager.java?rev=992030&r1=992029&r2=992030&view=diff
==============================================================================
--- myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/NullChangeManager.java
(original)
+++ myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/NullChangeManager.java
Thu Sep  2 17:41:53 2010
@@ -61,4 +61,13 @@ public class NullChangeManager extends C
   {
     //no-op
   }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
+  {
+    //no-op
+  }
 }

Modified: myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java?rev=992030&r1=992029&r2=992030&view=diff
==============================================================================
--- myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
(original)
+++ myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
Thu Sep  2 17:41:53 2010
@@ -22,51 +22,62 @@ import java.io.Serializable;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Queue;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.faces.component.NamingContainer;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 
+import org.apache.myfaces.trinidad.context.RequestContext;
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
 import org.apache.myfaces.trinidad.util.CollectionUtils;
 import org.apache.myfaces.trinidad.util.ComponentUtils;
 
+import org.apache.myfaces.trinidad.webapp.UIXComponentELTag;
+
 import org.w3c.dom.Document;
 
 
 /**
- * A ChangeManager implementation that manages
- *  persisting the added Changes at the session. This means
- *  the lifetime of Changes added such is within the session scope.
+ * A ChangeManager implementation that manages persisting the added Changes at the session.

+ * This means the lifetime of Changes added such is within the session scope. If any of the
changes
+ * are managed as state changes and restored by JSF state saving mechanism, the SessionChangeManager
+ * will not re-apply such changes. For example: AttributeComponentChanges are not applied
during
+ * a postback unless its target component happened to be a result of any move/add operation,
this is
+ * because attribute changes are handled by state manager during postback for common cases.
  * @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 $
  */
 public class SessionChangeManager extends BaseChangeManager
 {
   /**
    * {@inheritDoc}
-   * @param facesContext The FacesContext instance for the current request.
+   * @param context The FacesContext instance for the current request.
    */
   @Override
-  public void applyComponentChangesForCurrentView(FacesContext facesContext)
+  public void applyComponentChangesForCurrentView(FacesContext context)
   {
-    _applyComponentChanges(facesContext, null);
+    _applyComponentChanges(context, null);
   }
 
   /**
    * {@inheritDoc}
+   * @param context The FacesContext instance for the current request.
    */
    @Override
   public void applyComponentChangesForSubtree(
-    FacesContext facesContext,
+    FacesContext    context,
     NamingContainer root
     )
   {
@@ -80,165 +91,345 @@ public class SessionChangeManager extend
           "INVALID_TYPE", root));
       }
       
-      rootId = ComponentUtils.getScopedIdForComponent((UIComponent)root, null);
+      rootId = ComponentUtils.getScopedIdForComponent((UIComponent)root, context.getViewRoot());
     }
 
-    _applyComponentChanges(facesContext, rootId);
+    _applyComponentChanges(context, rootId);
+  }
+  
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
+  {
+    // we don't need to apply the component changes if we restored the state, since the
+    // attributes will be up-to-date
+    if (!_isStateRestored(context))
+    {
+      String         sessionKey     = _getSessionKey(context);
+      ChangesForView changesForView = _getChangesForView(context, sessionKey, false);
+      
+      if (changesForView != null)
+      {
+        changesForView.applyComponentChanges(context, component);
+      }
+    }
   }
 
   /**
    * Adds a ComponentChange and registers against the supplied component.
    * Changes added thus live at Session scope.
    * Use applyComponentChangesForCurrentView() to apply these changes.
-   * @param facesContext The FacesContext instance for the current request.
+   * @param context The FacesContext instance for the current request.
    * @param targetComponent The target component against which this change needs 
    * to be registered and applied later on.
    * @param componentChange The ComponentChange to add.
    */
   protected void addComponentChangeImpl(
-    FacesContext facesContext,
-    UIComponent targetComponent,
+    FacesContext    context,
+    UIComponent     targetComponent,
     ComponentChange componentChange)
+  {    
+    // try to collapse AttributeComponentChanges, handling component movement so that
+    // we can collapse any attribute change on the same component
+    if (componentChange instanceof AttributeComponentChange)
+    {
+      _replaceAttributeChange(context,
+                              targetComponent,
+                              (AttributeComponentChange)componentChange,
+                              false); // replace no matter what
+    }
+    else
+    {
+      // get the absolute scopedId for the target component so that we have a unique identifier
+      // to compare
+      String scopedIdForTargetComponent = 
+        ComponentUtils.getScopedIdForComponent(targetComponent, context.getViewRoot());
+
+      String         sessionKey     = _getSessionKey(context);
+      ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
+      
+      _insertComponentChange(changesForView, scopedIdForTargetComponent, componentChange);
+      
+      // dirty the key in the session for failover
+      context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
+    }
+  }
+  
+  /** 
+   * We don't support DocumentChange persistence
+   */
+  @Override
+  protected Document getDocument(FacesContext context)
   {
-    String viewId = facesContext.getViewRoot().getViewId();
-    
-    // get the ComponentChanges for the current viewId
-    ChangesForView changesForView = _getChangesForView(facesContext, viewId, true);
+    return null;
+  }
 
+  /**
+   *
+   * @param context
+   * @param component
+   * @param attributeComponentChange
+   * @param onlyIfPresent If true, we only insert a new changed if we removed an old one
+   * @return the removed AttributeComponentChange, if any
+   */
+  private AttributeComponentChange _replaceAttributeChange(
+    FacesContext             context,
+    UIComponent              component,
+    AttributeComponentChange attributeComponentChange,
+    boolean                  onlyIfPresent)
+  {    
     // get the absolute scopedId for the target component so that we have a unique identifier
     // to compare
-    String scopedIdForTargetComponent = 
-                                     ComponentUtils.getScopedIdForComponent(targetComponent,
null);
+    String scopedIdForTargetComponent = ComponentUtils.getScopedIdForComponent(
+                                                                            component,
+                                                                            context.getViewRoot());
+    
+    // check if we have an existing attribute change for the same attribute name, 
+    // if found, remove it
+    String         sessionKey     = _getSessionKey(context);
+    ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
 
-    // try to collapse AttributeComponentChanges, handling component movement so that
-    // we can collapse any attribute change on the same component
-    if (componentChange instanceof AttributeComponentChange)
+    AttributeComponentChange replaced = _extractAttributeChange(changesForView, 
+                                                                scopedIdForTargetComponent,

+                                                                attributeComponentChange);
+    
+    // if found, we insert the new change instance
+    if (!onlyIfPresent || (replaced != null))
     {
-      AttributeComponentChange attributeChange = (AttributeComponentChange)componentChange;
-      String attributeName = attributeChange.getAttributeName();
- 
-      // would really rather use a Deque here and iterate backwards, which would also make
-      // handling the rename changes easier
-      Iterator<QualifiedComponentChange> changes =
-                                            changesForView.getComponentChangesForView().iterator();
-      
-      // list of changes that have renamed the scoped id of this component.  We need to
-      // handle this aliasing when traversing through the changes looking for matches
-      Iterator<MoveChildComponentChange> renameChanges =
-                                       changesForView.getRenameChanges(scopedIdForTargetComponent);
-      
-      // we need to look through the rename list to map from the current names to
-      // the new names
-      MoveChildComponentChange nextRenameChange;
-      String currTargetScopedId;
-      
-      if (renameChanges.hasNext())
-      {
-        // we have at least one rename change, so get it and find the name that this
-        // component was originally known by
-        nextRenameChange = renameChanges.next();
-        currTargetScopedId = nextRenameChange.getSourceScopedId();
-      }
-      else
+      _insertComponentChange(changesForView, scopedIdForTargetComponent, attributeComponentChange);
+
+      // dirty the key in the session for failover
+      context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
+    }
+    
+    return replaced;
+  }  
+
+  /**
+   * Check if we have an existing attribute change for the same attribute name: 
+   * - if not found, return null
+   * - if found, remove and return the old change instance
+   * 
+   * @param changesForView
+   * @param scopedIdForTargetComponent
+   * @param attributeChange
+   * @return the old change instance, null if not found
+   */
+  private AttributeComponentChange _extractAttributeChange(
+    ChangesForView           changesForView,
+    String                   scopedIdForTargetComponent,
+    AttributeComponentChange attributeChange)
+  {
+    AttributeComponentChange extracted = null;
+        
+    String attributeName = attributeChange.getAttributeName();
+
+    // would really rather use a Deque here and iterate backwards, which would also make
+    // handling the rename changes easier
+    Iterator<QualifiedComponentChange> changes =
+                                          changesForView.getComponentChangesForView().iterator();
+    
+    // list of changes that have renamed the scoped id of this component.  We need to
+    // handle this aliasing when traversing through the changes looking for matches
+    Iterator<MoveChildComponentChange> renameChanges =
+                                     changesForView.getRenameChanges(scopedIdForTargetComponent);
+    
+    // we need to look through the rename list to map from the current names to
+    // the new names
+    MoveChildComponentChange nextRenameChange;
+    String currTargetScopedId;
+    
+    if (renameChanges.hasNext())
+    {
+      // we have at least one rename change, so get it and find the name that this
+      // component was originally known by
+      nextRenameChange = renameChanges.next();
+      currTargetScopedId = nextRenameChange.getSourceScopedId();
+    }
+    else
+    {
+      nextRenameChange = null;
+      currTargetScopedId = scopedIdForTargetComponent;
+    }
+    
+    // loop forward through the changes looking for AttributeChanges to collapse
+    while (changes.hasNext())
+    {
+      QualifiedComponentChange currQualifiedChange = changes.next();
+      
+      if (currQualifiedChange.getComponentChange() == nextRenameChange)
       {
-        nextRenameChange = null;
-        currTargetScopedId = scopedIdForTargetComponent;
+        // we got a match, so update the scoped id we should be looking for
+        currTargetScopedId = nextRenameChange.getDestinationScopedId();
+        
+        nextRenameChange = (renameChanges.hasNext())
+                             ? renameChanges.next()
+                             : null;
       }
-      
-      // loop forward through the changes looking for AttributeChanges to collapse
-      while (changes.hasNext())
+      else if (currQualifiedChange.getTargetComponentScopedId().equals(currTargetScopedId))
       {
-        QualifiedComponentChange currQualifiedChange = changes.next();
+        // found a change on the same component.  Check if it's an AttributeChange
+        ComponentChange currChange = currQualifiedChange.getComponentChange();
         
-        if (currQualifiedChange.getComponentChange() == nextRenameChange)
-        {
-          // we got a match, so update the scoped id we should be looking for
-          currTargetScopedId = nextRenameChange.getDestinationScopedId();
-          
-          nextRenameChange = (renameChanges.hasNext())
-                               ? renameChanges.next()
-                               : null;
-        }
-        else if (currQualifiedChange.getTargetComponentScopedId().equals(currTargetScopedId))
+        if (currChange instanceof AttributeComponentChange)
         {
-          // found a change on the same component.  Check if it's an AttributeChange
-          ComponentChange currChange = currQualifiedChange.getComponentChange();
+          AttributeComponentChange currAttributeChange = (AttributeComponentChange)currChange;
           
-          if (currChange instanceof AttributeComponentChange)
+          // Check if the AttributeChange is for the same attribute
+          if (attributeName.equals(currAttributeChange.getAttributeName()))
           {
-            AttributeComponentChange currAttributeChange = (AttributeComponentChange)currChange;
-            
-            // Check if the AttributeChange is for the same attribute
-            if (attributeName.equals(currAttributeChange.getAttributeName()))
-            {
-              // the old AttributeChange is for the same attribute, so remove it since the
-              // new AttributeChange would eclipse it anyway.
-              changes.remove();
-              break;
-            }
+            // the old AttributeChange is for the same attribute, so remove it since the
+            // new AttributeChange would eclipse it anyway.
+            changes.remove();
+            extracted = currAttributeChange;
+            break;
           }
         }
       }
     }
 
-    QualifiedComponentChange newQualifiedChange = new QualifiedComponentChange(
-                                                                      scopedIdForTargetComponent,
-                                                                      componentChange);
-    
-    changesForView.addChange(newQualifiedChange);
+    return extracted;    
   }
 
-  /** 
-   * We don't support DocumentChange persistence
+  /**
+   * insert a component change for a specific component
+   * 
+   * @param changesForView
+   * @param scopedIdForTargetComponent
+   * @param componentChange
    */
-  @Override
-  protected Document getDocument(FacesContext context)
+  private void _insertComponentChange(ChangesForView changesForView,
+                                      String scopedIdForTargetComponent,
+                                      ComponentChange componentChange) 
   {
-    return null;
+    QualifiedComponentChange newQualifiedChange = 
+      new QualifiedComponentChange(scopedIdForTargetComponent,
+                                   componentChange);
+    
+    changesForView.addChange(newQualifiedChange);
   }
-  
+
   /**
    * Implementation shared by applyComponentChangesForCurrentView() and
    * applyComponentChangesForSubtree().
-   * @param facesContext The FacesContext instance for this request.
+   * @param context The FacesContext instance for this request.
    * @param rootId The scoped id of theNamingContainer that contains the 
    * component subtree to which ComponentChanges should be applied.  If null, 
    * all changes are applied.
    */
   private void _applyComponentChanges(
-    FacesContext facesContext,
-    String       rootId
+    FacesContext   context,
+    String         rootId
     )
   {
-    UIViewRoot viewRoot = facesContext.getViewRoot();
+    ChangesForView changesForView = _getReadOnlyChangesForView(context);
+    
+    UIViewRoot viewRoot = context.getViewRoot();
     
-    // retrieve the ComponentChanges for this current viewid
-    ChangesForView changesForView = _getChangesForView(facesContext, viewRoot.getViewId(),
false);
+    // retrieve the ComponentChanges for this current viewid    
+    boolean isStateRestored = _isStateRestored(context);
+    
+    // components that have their attribute application forced because a change that would
confuse
+    // state saving has been applied
+    Set<String> attributeForcedComponents = new HashSet<String>();
     
     // loop through the viewId's changes, applying the changes
     for (QualifiedComponentChange qualifiedChange : changesForView.getComponentChangesForView())
     {
-      if (!_acceptChange(qualifiedChange, rootId))
-        continue;
+      ComponentChange componentChange = qualifiedChange.getComponentChange();
+      String targetComponentScopedId  = qualifiedChange.getTargetComponentScopedId();
 
-      UIComponent targetComponent = 
-        viewRoot.findComponent(qualifiedChange.getTargetComponentScopedId());
-      
-      // Possible that the target component no more exists in the view
-      if (targetComponent != null)
+      // We do not apply attribute changes if it is a postback, because we expect that
+      // 1. Users calling ChangeManager.addComponentChange() would also apply the change
right at
+      //  that point in time (maybe by calling ComponentChange.changeComponent() method).
+      // 2. If #1 was done, JSF state manager will consider this a state change and will
store and
+      //  restore it during subsequent postbacks, so there is no need for applying attribute
changes
+      //  for postback cases. There are few exceptions where the state management will not
help, for
+      //  which we force the attribute changes even when it is a postback.
+      if (isStateRestored &&
+          componentChange instanceof AttributeComponentChange &&
+          !attributeForcedComponents.contains(targetComponentScopedId))
       {
-        ComponentChange componentChange = qualifiedChange.getComponentChange();
-        componentChange.changeComponent(targetComponent);
+        continue;
       }
-      else
+      
+      // If change target for the qualified change is not inside of the specified root, skip
+      if (!_acceptChange(qualifiedChange, rootId))
+        continue;
+      
+      UIComponent targetComponent = viewRoot.findComponent(targetComponentScopedId);
+      
+      // Possible that the target component no more exists in the view, if yes, skip
+      if (targetComponent == null)
       {
         _LOG.info(this.getClass().getName(),
                   "applyComponentChangesForCurrentView",
                   "TARGET_COMPONENT_MISSING_CHANGE_FAILED",
-                  qualifiedChange.getTargetComponentScopedId());
+                  targetComponentScopedId);
+        continue;
+      }
+     
+      // Apply the change
+      componentChange.changeComponent(targetComponent);
+      
+      // Now that the change is applied, we can identify if the components altered by the
currently
+      //  applied change needs forced application of any further changes regardless of request

+      //  being a postback.
+      if (componentChange instanceof MoveChildComponentChange)
+      {
+        String destinationScopedId = 
+                             ((MoveChildComponentChange)componentChange).getDestinationScopedId();
+        
+        // we no longer need to force the old scoped id, if any, but we do need to force
the new one
+        attributeForcedComponents.remove(targetComponentScopedId);
+        attributeForcedComponents.add(destinationScopedId);
+      }
+      else if (componentChange instanceof SetFacetChildComponentChange)
+      {
+        String facetName = ((SetFacetChildComponentChange)componentChange).getFacetName();
+        UIComponent facetComponent = targetComponent.getFacet(facetName);
+        
+        if (facetComponent != null)
+        {
+          String facetScopedId = ComponentUtils.getScopedIdForComponent(facetComponent, viewRoot);
+          
+          attributeForcedComponents.add(facetScopedId);
+        }
       }
-    }    
+      else if (componentChange instanceof AddChildComponentChange)
+      {
+        // Get the added component from AddComponentChange, this component is actually re-created

+        //  from the proxy, and not the actual added component. 
+        //  Bit hacky but this is only way to get Id.
+        String addedComponentId = ((AddChildComponentChange)componentChange).getComponent().getId();
+        
+        // Now get the actual added component finding from the parent to which it was added
to
+        UIComponent addedComponent = ComponentUtils.findRelativeComponent(targetComponent,
+                                                                          addedComponentId);
+                
+        if (addedComponent != null)
+        {
+          String addedChildComponentScopedId= ComponentUtils.getScopedIdForComponent(addedComponent,
+                                                                                     viewRoot);
+          attributeForcedComponents.add(addedChildComponentScopedId);
+        }
+      }
+    }  
   }
+  
+  /**
+   * Is the state restored by JSF state manager in this request. This is usually true if
this is a
+   *  postback request. Additionally check if the document tag created a document component,
because
+   *  if this is the case, we are sure that there was no state restoration.
+   */
+  private boolean _isStateRestored(FacesContext facesContext)
+  {
+    boolean docCompCreated = Boolean.TRUE.equals(facesContext.getExternalContext().
+                                   getRequestMap().get(UIXComponentELTag.DOCUMENT_CREATED_KEY));
+    return (docCompCreated) ? false : RequestContext.getCurrentInstance().isPostback();
+  }  
 
   /**
    * Tests whether the specified change should be applied based on the
@@ -257,21 +448,28 @@ public class SessionChangeManager extend
     String rootId
     )
   {
-    boolean accept = true;
-
     if (rootId != null)
     {
       String id = qualifiedChange.getTargetComponentScopedId();
-      accept = (id.startsWith(rootId) && (id.length() != rootId.length()));    
+      return (id.startsWith(rootId) && (id.length() != rootId.length()));    
+    }
+    else
+    {
+      return true;
     }
+  }
+
+  private ChangesForView _getReadOnlyChangesForView(FacesContext context)
+  {
+    String sessionKey = _getSessionKey(context);
     
-    return accept;
+    return _getChangesForView(context, sessionKey, false);
   }
 
   /**
    * Gets the in-order list of component changes for the given view.
-   * @param facesContext The FacesContext instance for this request.
-   * @param viewId The id of the view for which changes are required.
+   * @param context The FacesContext instance for this request.
+   * @param sessionKey The composite session key based on the viewId 
    * @param createIfNecessary Indicates whether the underlying datastructures
    * that store the component changes needs to be created if absent.
    * @return The ChangesForView object containing information about the changes for the specified
@@ -280,46 +478,69 @@ public class SessionChangeManager extend
    * calls to <code>addComponentChange()</code>.
    */
   private ChangesForView _getChangesForView(
-    FacesContext facesContext,
-    String viewId,
-    boolean createIfNecessary)
+    FacesContext context,
+    String       sessionKey,
+    boolean      createIfNecessary)
   {
-    Object session = facesContext.getExternalContext().getSession(true);
-    
-    // Key is view id and value is list of component changes for that view
-    ConcurrentHashMap<String, ChangesForView> componentChangesMapForSession;
+    ExternalContext extContext = context.getExternalContext();
+    Map<String, Object> sessionMap = extContext.getSessionMap();
+
+    Object changes = sessionMap.get(sessionKey);
     
-    synchronized(session)
+    if (changes == null)
     {
-      Map<String, Object> sessMap = 
-        facesContext.getExternalContext().getSessionMap();
-      
-      componentChangesMapForSession = (ConcurrentHashMap<String, ChangesForView>)
-                                       sessMap.get(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY);
-      
-      if (componentChangesMapForSession == null)
+      if (createIfNecessary)
       {
-        if (!createIfNecessary)
-          return _EMPTY_CHANGES;
-  
-        componentChangesMapForSession = new ConcurrentHashMap<String, ChangesForView>();
-        sessMap.put(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY, 
-                    componentChangesMapForSession);
+        // make sure we only create this viewId's changes entry once
+        Object session = extContext.getSession(true);
+        
+        synchronized (session)
+        {
+          changes = sessionMap.get(sessionKey);
+          
+          if (changes == null)
+          {
+            ChangesForView changesForView = new ChangesForView(true);
+            
+            sessionMap.put(sessionKey, changesForView);
+            
+            return changesForView;  // return the newly created changes
+          }
+          else
+          {
+            return (ChangesForView)changes;  // another thread created the changes for us
         
+          }
+        }
+      }
+      else
+      {
+        return _EMPTY_CHANGES;  // no changes and we aren't allowed to create them
       }
     }
-
-    if (!componentChangesMapForSession.containsKey(viewId))
+    else
     {
-      if (!createIfNecessary)
-        return _EMPTY_CHANGES;
-      
-      componentChangesMapForSession.putIfAbsent(viewId, new ChangesForView(true));
+      return (ChangesForView)changes;  // we have the changes
     }
-    
-    return componentChangesMapForSession.get(viewId);
   }
   
   /**
+   * Return the Session key to store the changes for this viewId in.  We store each viewId
under
+   * a different key to avoid needing to failover all of the changes whenever the changes
for
+   * a particular viewId are modified.
+   * @param context
+   * @return
+   */
+  private String _getSessionKey(FacesContext context)
+  {
+    String viewId = context.getViewRoot().getViewId();
+    
+    StringBuilder builder = new StringBuilder(viewId.length() +
+                                              _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY.length());
+    
+    return builder.append(_COMPONENT_CHANGES_MAP_FOR_SESSION_KEY).append(viewId).toString();
+  }
+
+  /**
    * Tracks the component changes for a particular view as well as all the movement
    * changes so that component aliasing can be tracked
    */
@@ -335,9 +556,37 @@ public class SessionChangeManager extend
       else
       {
         _componentChangesForView = CollectionUtils.emptyQueue();
-        _renameChanges = null;
+        _renameChanges = Collections.emptyList();
       }
     }
+
+    @Override
+    public String toString()
+    {
+      return super.toString() + "[componentChange=" + _componentChangesForView +
+             " renameChanges=" + _renameChanges + "]";
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+      if (o == this)
+        return true;
+      
+      if (!(o instanceof ChangesForView))
+        return false;
+      
+      ChangesForView other = (ChangesForView)o;
+      
+      return _componentChangesForView.equals(other._componentChangesForView) &&
+             _renameChanges.equals(other._renameChanges);
+    }
+    
+    @Override
+    public int hashCode()
+    {
+      return _componentChangesForView.hashCode() + 37 * _renameChanges.hashCode();
+    }
     
     /** 
      * Returns the QualifiedComponentChanges for this viewId
@@ -354,33 +603,50 @@ public class SessionChangeManager extend
      */
     protected void addChange(QualifiedComponentChange qualifiedChange)
     {
-      _componentChangesForView.add(qualifiedChange);
-      
-      ComponentChange componentChange = qualifiedChange.getComponentChange();
-      
-      if (componentChange instanceof MoveChildComponentChange)
+      // make sure that we don't add changes while getAttrChanges() is rebuilding the
+      // per-component changes
+      synchronized(_attrRebuildLock)
       {
-        // we only need to remove moves that actually changed the absolute scoped id of the
-        // component
-        MoveChildComponentChange moveComponentChange = (MoveChildComponentChange)componentChange;
+        _componentChangesForView.add(qualifiedChange);
+        
+        ComponentChange componentChange = qualifiedChange.getComponentChange();
+
+        // make sure that the attr change and rename maps are up-to-date
+        ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> attrChanges=_getAttrChanges();
         
-        if (!moveComponentChange.getSourceScopedId().equals(moveComponentChange.getDestinationScopedId()))
+        if (componentChange instanceof AttributeComponentChange)
+        {
+          // update the attribute changes with the current change
+          _updateAttributeChange(attrChanges, _renameMap, qualifiedChange);
+        }
+        else if (componentChange instanceof MoveChildComponentChange)
         {
-          _renameChanges.add(moveComponentChange);
+          // we only need to remove moves that actually changed the absolute scoped id of
the
+          // component
+          MoveChildComponentChange moveComponentChange = (MoveChildComponentChange)componentChange;
+          
+          if (!moveComponentChange.getSourceScopedId().equals(moveComponentChange.getDestinationScopedId()))
+          {
+            _renameChanges.add(moveComponentChange);
+            
+            // update the rename map to account for this change
+            
+            _updateRenameMap(_renameMap, moveComponentChange);
+          }
         }
       }
     }
-
+      
     /**
      * Returns the Iterator of rename changes that affect the current scoped id in ComponentChange
order
      * @return
      */
     protected Iterator<MoveChildComponentChange> getRenameChanges(String targetScopedId)
     {
-      if (_renameChanges != null)
+      if (!_renameChanges.isEmpty())
       {
         String currTargetScopedId = targetScopedId;
-        List renameChanges = null;
+        List<MoveChildComponentChange> renameChanges = null;
         
         // iterate from the back of the List determining the MoveChildComponentChange
         // that are aliased to this scoped id
@@ -419,9 +685,156 @@ public class SessionChangeManager extend
       
       return CollectionUtils.emptyIterator();
     }
+
+    /**
+     * Apply the attribute changes for this component
+     * @param context
+     * @param component
+     */
+    protected void applyComponentChanges(FacesContext context, UIComponent component)
+    {
+      String scopedId = ComponentUtils.getScopedIdForComponent(component, context.getViewRoot());
+      
+      ConcurrentMap<String, ComponentChange> componentChanges = _getAttrChanges().get(scopedId);
+      
+      if (componentChanges != null)
+      {
+        for (ComponentChange change : componentChanges.values())
+        {
+          change.changeComponent(component);
+        }
+      }
+    }
+
+    /**
+     * Return the Map of original component scopedIds to the Map of their attribute names
to
+     * AttributeComponentChanges, building the mapping if necessary
+     * @return
+     */
+    private ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> _getAttrChanges()
+    {
+      // if we don't have a map of scopedId to attribute changes then it is because this
is
+      // either the first request for this session or we failed over.  In either case, build
+      // the list (which will essentially be fast in the first case.
+      if (_attrChanges == null)
+      {
+        synchronized(_attrRebuildLock)
+        {
+          // double check that no one else has completed our request
+          if (_attrChanges == null)
+          {
+            ConcurrentMap<String, String> renameMap = new ConcurrentHashMap<String,
String>();
+            
+            ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> attrChanges
= new
+              ConcurrentHashMap<String, ConcurrentMap<String, ComponentChange>>();
+            
+            for (QualifiedComponentChange currQChange: getComponentChangesForView())
+            {
+              ComponentChange currComponentChange = currQChange.getComponentChange();
+              
+              if (currComponentChange instanceof AttributeComponentChange)
+              {
+                // update the attribute changes with the current change
+                _updateAttributeChange(attrChanges, renameMap, currQChange);
+              }
+              else if (currComponentChange instanceof MoveChildComponentChange)
+              {
+                // update the rename list from the new name back to the original scoped name
+                _updateRenameMap(renameMap, (MoveChildComponentChange)currComponentChange);
+              }
+            }
+           
+            _renameMap   = renameMap;
+            _attrChanges = attrChanges;
+          }
+        }
+      }
+      
+      return _attrChanges;
+    }
+
+    private void _updateAttributeChange(
+      ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> attrChanges,
+      ConcurrentMap<String, String>                                 renameMap,
+      QualifiedComponentChange                                      qAttrChange)
+    {
+      // update the current attribute values for the scoped id
+      String currScopedId = qAttrChange.getTargetComponentScopedId();
+      
+      // apply any move rename
+      String originalScopedId = renameMap.get(currScopedId);
+      
+      // we don't add rename mapping until a move, so if there is no entry, the origina
+      // value is good
+      if (originalScopedId == null)
+        originalScopedId = currScopedId;
+      
+      // get the map for this component, creating one if necessary
+      ConcurrentMap<String, ComponentChange> changesForComponent = 
+                                                           attrChanges.get(originalScopedId);
+      
+      // if we haven't registered a Map yet, create one and register it
+      if (changesForComponent == null)
+      {
+        // =-= bts There probably aren't that many different changes per component.  Maybe
+        //         we need something smaller and more efficient here
+        changesForComponent = new ConcurrentHashMap<String, ComponentChange>();
+        attrChanges.put(originalScopedId, changesForComponent);
+      }
+      
+      AttributeComponentChange attrChange = (AttributeComponentChange)
+                                            qAttrChange.getComponentChange();
+      
+      // update the current AttributeComponentChange for this attribute
+      String attrName = attrChange.getAttributeName();
+      
+      changesForComponent.put(attrName, attrChange);
+      
+    }
     
+    /**
+     * Update the renamemap with a change
+     * @param renameMap
+     * @param moveChange
+     */
+    private void _updateRenameMap(
+      ConcurrentMap<String, String> renameMap,
+      MoveChildComponentChange      moveChange)
+    {
+      String sourceScopedId      = moveChange.getSourceScopedId();
+      String destinationScopedId = moveChange.getDestinationScopedId();
+      
+      // we only need to update the map if we actually changed scoped ids
+      if (!(sourceScopedId.equals(destinationScopedId)))
+      {
+        // remove the old mapping for source
+        String originalScodeId = renameMap.remove(sourceScopedId);
+        
+        // we don't bother adding mappings if there has been no rename, plus there might
+        // not be any attribute changes yet.  In this case, the source scoped id must
+        // be the original id
+        if (originalScodeId == null)
+          originalScodeId = sourceScopedId;
+        
+        // add the new, updated mapping to account for the move
+        renameMap.put(destinationScopedId, originalScodeId);
+      }
+    }
+     
     private final Queue<QualifiedComponentChange> _componentChangesForView;
     private final List<MoveChildComponentChange> _renameChanges;
+    
+    // Lock used to synchronize rebuilding of the _attrChanges and _renameMap
+    private transient final Object _attrRebuildLock = new Object();
+    
+    // map of original scopedIds to Map of attribute names and their new values.  This allows
+    // us to apply all of attribute changes efficiently
+    private transient volatile ConcurrentMap<String,
+                                             ConcurrentMap<String, ComponentChange>>
_attrChanges;
+    
+    // map of current scoped ids to original scoped ids.  This enables us to correctly update
+    // the attributes for the original scoped ids even after the component has moved
+    private transient volatile ConcurrentMap<String, String> _renameMap;
 
     private static final long serialVersionUID = 1L;
   }
@@ -451,6 +864,34 @@ public class SessionChangeManager extend
     {
       return _componentChange;
     }
+    
+    @Override
+    public boolean equals(Object o)
+    {
+      if (o == this)
+        return true;
+      
+      if (!(o instanceof QualifiedComponentChange))
+        return false;
+      
+      QualifiedComponentChange other = (QualifiedComponentChange)o;
+      
+      return _targetComponentScopedId.equals(other._targetComponentScopedId) &&
+             _componentChange.equals(other._componentChange);
+    }
+    
+    @Override
+    public int hashCode()
+    {
+      return _targetComponentScopedId.hashCode() + 37 * _componentChange.hashCode();
+    }
+        
+    @Override
+    public String toString()
+    {
+      return super.toString() + "[target=" + _targetComponentScopedId +
+              " change=" + _componentChange + "]";
+    }
 
     private final String _targetComponentScopedId;
     private final ComponentChange _componentChange;
@@ -460,7 +901,7 @@ public class SessionChangeManager extend
   
   private static final String _COMPONENT_CHANGES_MAP_FOR_SESSION_KEY =
     "org.apache.myfaces.trinidadinternal.ComponentChangesMapForSession";
-    
+      
   private static final TrinidadLogger _LOG = 
     TrinidadLogger.createTrinidadLogger(SessionChangeManager.class);
 }

Modified: myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java?rev=992030&r1=992029&r2=992030&view=diff
==============================================================================
--- myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java
(original)
+++ myfaces/trinidad/branches/trinidad-1.2.x/trinidad-api/src/main/java/org/apache/myfaces/trinidad/webapp/UIXComponentELTag.java
Thu Sep  2 17:41:53 2010
@@ -35,6 +35,7 @@ import javax.el.ValueExpression;
 
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIViewRoot;
+import javax.faces.context.ExternalContext;
 import javax.faces.context.FacesContext;
 import javax.faces.webapp.UIComponentELTag;
 
@@ -86,6 +87,12 @@ abstract public class UIXComponentELTag 
     //  created. End of document tag is a best bet.
     if (component instanceof UIXDocument)
     {
+      if (getCreated()) 
+      {
+        ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
+        // Used by SessionChangeManager to confirm that the state was not restored.
+        ec.getRequestMap().put(DOCUMENT_CREATED_KEY, Boolean.TRUE);
+      }
       ChangeManager cm = RequestContext.getCurrentInstance().getChangeManager();
       cm.applyComponentChangesForCurrentView(FacesContext.getCurrentInstance());
     }
@@ -488,6 +495,8 @@ abstract public class UIXComponentELTag 
     return sdf;
   }
 
+  public static final String DOCUMENT_CREATED_KEY = "org.apache.myfaces.trinidad.DOCUMENTCREATED";
+
   private MethodExpression  _attributeChangeListener;
   private String            _validationError;
 }



Mime
View raw message