You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by gc...@apache.org on 2013/05/17 22:33:02 UTC

svn commit: r1483979 - in /myfaces/trinidad/trunk/trinidad-api/src/main: java/org/apache/myfaces/trinidad/change/ xrts/org/apache/myfaces/trinidad/resource/

Author: gcrawford
Date: Fri May 17 20:33:01 2013
New Revision: 1483979

URL: http://svn.apache.org/r1483979
Log:
TRINIDAD-2384 Need a notification mechanism for successful application of DocumentChange 

Thanks to Prakash

Modified:
    myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/BaseChangeManager.java
    myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
    myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManagerWrapper.java
    myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
    myfaces/trinidad/trunk/trinidad-api/src/main/xrts/org/apache/myfaces/trinidad/resource/LoggerBundle.xrts

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/BaseChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/BaseChangeManager.java?rev=1483979&r1=1483978&r2=1483979&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/BaseChangeManager.java (original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/BaseChangeManager.java Fri May 17 20:33:01 2013
@@ -22,10 +22,9 @@ import javax.faces.component.UIComponent
 import javax.faces.context.FacesContext;
 
 import org.apache.myfaces.trinidad.component.UIXIterator;
+import org.apache.myfaces.trinidad.context.RequestContext;
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
 
-import org.apache.myfaces.trinidad.util.ComponentUtils;
-
 import org.w3c.dom.Document;
 
 
@@ -53,7 +52,7 @@ abstract class BaseChangeManager extends
     // don't want to persist the changes 
     UIComponent parent = uiComponent.getParent();
     UIComponent root = facesContext.getViewRoot();
-    while (parent != null && parent != root) 
+    while (parent != null && parent != root)
     {
       if (parent.getClass() == UIXIterator.class) 
       {
@@ -70,32 +69,15 @@ abstract class BaseChangeManager extends
     // add the change to the component
     addComponentChangeImpl(facesContext, uiComponent, change);
 
-    if (supportsDocumentPersistence(facesContext))
-    {
-      DocumentChange docChange = null;
-
-      if (change instanceof DocumentChange)
-      {
-        docChange = (DocumentChange)change;
-      }
-      else
-      {
-        // try to get equivalent DocumentChange from ComponentChange
-        docChange = createDocumentChange(change);
-      }
-
-      if (docChange != null)
-      {
-        addDocumentChange(facesContext, uiComponent, docChange);
-      }
-    }
+    // add a corresponding DocumentChange if possible
+    _addEquivalentDocumentChange(facesContext, uiComponent, change);
   }
-
+  
   /**
    * A no-op implementation of adding a ComponentChange. Sub-classers should
    * override and provide an implementation if they support component changes.
    * @param facesContext The FacesContext for this request.
-   * @param targetComponent The target component against which this change needs
+   * @param targetComponent The target component against which this change needs 
    * to be registered and applied later on.
    * @param componentChange The ComponentChange to add
    */
@@ -123,7 +105,7 @@ abstract class BaseChangeManager extends
    * in order to enable  Document-based Persistence
    */
   protected abstract Document getDocument(FacesContext context);
-
+  
   /**
    *  Returns true if we can support Document-based Persistence
    *  in this ChangeManager.  Subclassers adding Document-based Persistence
@@ -137,6 +119,46 @@ abstract class BaseChangeManager extends
     // correct, but potentially slow implementation
     return getDocument(context) != null;
   }
+
+  /*
+   * Create and add a DocumentChange corresponding to the supplied ComponentChange
+   */
+  private void _addEquivalentDocumentChange(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    ComponentChange compChange)
+  {
+    if (supportsDocumentPersistence(facesContext))
+    {
+      DocumentChange docChange = null;
+if (compChange instanceof DocumentChange)
+      
+      {
+        docChange = (DocumentChange)compChange;
+      }
+      else
+      {
+        // try to get equivalent DocumentChange from ComponentChange
+        docChange = createDocumentChange(compChange);
+      }
+
+      if (docChange != null)
+      {
+        //  We ("this" object) could be just a delegate of the top level change manager (i.e. the 
+        //  changemanager registered with the webapp). Automatically adding a document change (or 
+        //  deciding whether to add one) should be done by the top level changemanager 
+        ChangeManager topChangeManager = RequestContext.getCurrentInstance().getChangeManager();
+        
+        if ( ChangeOutcome.CHANGE_APPLIED ==
+              topChangeManager.addDocumentChangeWithOutcome(facesContext, uiComponent, docChange) )
+        {
+          // notify the registered change manager that we applied the document change 
+          topChangeManager.documentChangeApplied(facesContext, uiComponent, compChange);
+        }
+      }
+    }    
+  }
+  
   private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
     BaseChangeManager.class);
 }

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java?rev=1483979&r1=1483978&r2=1483979&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java (original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManager.java Fri May 17 20:33:01 2013
@@ -36,6 +36,8 @@ import org.apache.myfaces.trinidad.loggi
  *  take care of their persistence.
  * @version $Name:  $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/change/ChangeManager.java#0 $) $Date: 10-nov-2005.19:09:58 $
  */
+// WHENEVER A NEW METHOD IS ADDED TO THIS CLASS, REMEMBER TO ADD AN IMPLEMENTATION TO
+// org.apache.myfaces.trinidad.change.ChangeManagerWrapper
 public abstract class ChangeManager
 {
   public static void registerDocumentFactory(
@@ -108,7 +110,7 @@ public abstract class ChangeManager
           else
           {
             // log warning because class isn't correct type
-            _LOG.warning("CONVERSION_CLASS_TYPE", new Object[] {converterClass, DocumentChangeFactory.class}); // NOTRANS
+            _LOG.warning("CONVERSION_CLASS_TYPE", new Object[] {converterClass, DocumentChangeFactory.class});
           }
         }
         catch (Throwable e)
@@ -137,18 +139,31 @@ public abstract class ChangeManager
   }
 
   /**
-   * Add a ComponentChange to this current request for a specified component.
-   * When called we will disallow changes if the component or its any ancestor 
-   * is a stamped component by UIXIterator. 
-   *
-   * @throws IllegalArgumentException if any of the supplied parameters were to
-   *          be null.
+   * Adds a ComponentChange to the current request for a specified component. Component changes 
+   *  cannot be added for stamped children of an UIXIterator.
+   * 
+   * A DocumentChange will be automatically created and applied on the ChangeManager registered
+   *  for this application if the following conditions are met:
+   *  1. The ChangeManager registered for the application supports document change persistence
+   *  2. DocumentChange corresponding to the supplied ComponentChange can be created with the help
+   *      of any registered DocumentChangeFactory
+   * When such a DocumentChange is added, the ChangeManager registered for the application is
+   *  notified by means of calling its documentChangeApplied() method. This is to give the 
+   *  registered ChangeManager an opportunity to take any necessary action. For example, Session 
+   *  based ChangeManager implementations may choose to remove the ComponentChange, if any added 
+   *  earlier. Custom ChangeManager implementations should notify likewise if it automatically 
+   *  creates and adds a DocumentChange.
+   * 
+   * @throws IllegalArgumentException if any of the supplied parameters were to be null.
+   * 
+   * @see DocumentChangeFactory
+   * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange
    */
   public abstract void addComponentChange(
     FacesContext facesContext,
     UIComponent uiComponent,
     ComponentChange change);
-  
+
   /**
    * Replace an AttributeComponentChange if it's present. 
    * 
@@ -157,7 +172,8 @@ public abstract class ChangeManager
    * @param attributeComponentChange
    * @return the old change instance
    */
-  public AttributeComponentChange replaceAttributeChangeIfPresent(FacesContext facesContext,
+  public AttributeComponentChange replaceAttributeChangeIfPresent(
+    FacesContext facesContext,
     UIComponent uiComponent,
     AttributeComponentChange attributeComponentChange)
   {    
@@ -167,23 +183,78 @@ public abstract class ChangeManager
 
   /**
    * Add a DocumentChange to this current request for a specified component.
-   * When called we will allow changes even if the component or its any ancestor 
+   * When called we will allow changes even if the component or its any ancestor
    * is a stamped component by UIXIterator.
-   * 
+   *
    * @throws IllegalArgumentException if any of the supplied parameters were to
-   *          be null.
+   * be null.
+   *
+   * @deprecated use
+   * {@link ChangeManager#addDocumentChangeWithOutcome(javax.faces.context.FacesContext,javax.faces.component.UIComponent,org.apache.myfaces.trinidad.change.DocumentChange)}
+   * instead
    */
+  @Deprecated
   public void addDocumentChange(
-      FacesContext facesContext,
-      UIComponent uiComponent,
-      DocumentChange change)
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    DocumentChange change)
   {
     if (facesContext == null || uiComponent == null || change == null)
       throw new IllegalArgumentException(_LOG.getMessage(
         "CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL"));
   }
+  
+  /**
+   * Add a DocumentChange for a specified component, and return the outcome of adding the change.
+   * 
+   * @param facesContext  The FacesContext instance for the current request
+   * @param uiComponent   The UIComponent instance for which the DocumentChange is to be added
+   * @param change        The DocumentChange to be added
+   * 
+   * @return The outcome of adding the document change
+   * 
+   * @throws IllegalArgumentException if any of the supplied parameters were to
+   *          be null.
+   *          
+   * @see ChangeOutcome
+   */
+  public ChangeOutcome addDocumentChangeWithOutcome(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    DocumentChange change)
+  {
+    addDocumentChange(facesContext, uiComponent, change);
+
+    return ChangeOutcome.UNKNOWN;
+  }
 
   /**
+   * This method is called on the registered ChangeManager if a ChangeManager in its 
+   *  addComponentChange() implementation automatically creates an equivalent DocumentChange and
+   *  applies the change. The registered ChangeManager may choose to take some action based on 
+   *  the outcome of applying the document change. For example, session based ChangeManager
+   *  implementations may choose to remove any earlier added ComponentChange if an equivalent
+   *  document change is now successfully applied
+   *  
+   * @param component       The target UIComponent instance for which the DocumentChange was
+   *                         applied
+   * @param componentChange The ComponentChange for which an equivalent DocumentChange was applied
+   * 
+   * @return The outcome of handling this notification
+   * 
+   * @throws IllegalArgumentException if the supplied ComponentChange is null.   *          
+   */
+  public NotificationOutcome documentChangeApplied(
+    FacesContext facesContext,
+    UIComponent component,
+    ComponentChange componentChange)
+  {
+    if (componentChange == null)
+      throw new IllegalArgumentException("The supplied ComponentChange object is null"); 
+    return NotificationOutcome.NOT_HANDLED;
+  }
+  
+  /**
    * Applies all the ComponentChanges added so far for the current view.
    * Developers should not need to call this method. Internal implementation
    * will call it as the component tree is built and is ready to take changes.
@@ -208,12 +279,11 @@ public abstract class ChangeManager
    */
   public void applyComponentChangesForSubtree(
     FacesContext facesContext,
-    NamingContainer root
-    )
+    NamingContainer root)
   {
     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
@@ -226,6 +296,38 @@ public abstract class ChangeManager
     throw new UnsupportedOperationException("Subclassers must implement");    
   }
   
+  /**
+   * Indicates the outcome of the attempt to apply a Change. Possible outcomes are:
+   * 1. UNKNOWN - We do not know if the change was applied or not
+   * 2. CHANGE_APPLIED - Change was successfully applied
+   * 3. CHANGE_NOT_APPLIED - There was a failure when applying the Change
+   *
+   * @see #addDocumentChangeWithOutcome(FacesContext,UIComponent,DocumentChange)
+   */
+  public static enum ChangeOutcome
+  {
+    UNKNOWN,
+    CHANGE_APPLIED,
+    CHANGE_NOT_APPLIED;
+
+    private static final long serialVersionUID = 1L;
+  }
+
+  /**
+   * Indicates whether the notification was handled:
+   * 1. HANDLED - Notification was handled
+   * 2. NOT_HANDLED - Notification was not handled
+   * 
+   * @see #documentChangeApplied(FacesContext, UIComponent, ComponentChange)
+   */
+  public static enum NotificationOutcome
+  {
+    HANDLED,
+    NOT_HANDLED;
+
+    private static final long serialVersionUID = 1L;
+  }
+  
   private static class AttributeConverter extends DocumentChangeFactory
   {
     @Override

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManagerWrapper.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManagerWrapper.java?rev=1483979&r1=1483978&r2=1483979&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManagerWrapper.java (original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/ChangeManagerWrapper.java Fri May 17 20:33:01 2013
@@ -1,94 +1,135 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.myfaces.trinidad.change;
-
-import javax.faces.component.NamingContainer;
-import javax.faces.component.UIComponent;
-import javax.faces.context.FacesContext;
-
-import org.apache.myfaces.trinidad.change.ChangeManager;
-import org.apache.myfaces.trinidad.change.ComponentChange;
-import org.apache.myfaces.trinidad.change.DocumentChange;
-
-/**
- * Base class for ChangeManager implementations that wrap other ChangeManagers.
- */
-abstract public class ChangeManagerWrapper extends ChangeManager
-{
-  /**
-   * Delegates the addComponentChange() implementation to the wrapped
-   * ChangeManager.
-   */
-  @Override
-  public void addComponentChange(
-    FacesContext context,
-    UIComponent component,
-    ComponentChange change)
-  {
-    getWrappedChangeManager().addComponentChange(context, component, change);
-  }
-
-  /**
-   * Delegates the addDocumentChange() implementation to the wrapped
-   * ChangeManager.
-   */
-  @Override
-  public void addDocumentChange(
-    FacesContext context,
-    UIComponent component,
-    DocumentChange change)
-  {
-    getWrappedChangeManager().addDocumentChange(context, component, change);
-  }
-
-  /**
-   * Delegates the applyComponentChangesForCurrentView() implementation to 
-   * the wrapped ChangeManager.
-   */
-  @Override
-  public void applyComponentChangesForCurrentView(FacesContext context)
-  {
-    getWrappedChangeManager().applyComponentChangesForCurrentView(context);
-  }
-
-  /**
-   * Delegates the applyComponentChangesForSubtree() implementation to the 
-   * wrapped ChangeManager.
-   */
-  @Override
-  public void applyComponentChangesForSubtree(FacesContext context, NamingContainer root)
-  {
-    getWrappedChangeManager().applyComponentChangesForSubtree(context, root);
-  }
-
-  /**
-   * Delegates the applySimpleComponentChanges() implementation to the wrapped
-   * ChangeManager.
-   */
-  @Override
-  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
-  {
-    getWrappedChangeManager().applySimpleComponentChanges(context, component);
-  }
-
-  /**  
-   * Returns the wrapped ChangeManager.
-   */  
-  abstract protected ChangeManager getWrappedChangeManager();
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.myfaces.trinidad.change;
+
+import javax.faces.component.NamingContainer;
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+
+
+/**
+ * Base class for ChangeManager implementations that wrap other ChangeManagers.
+ */
+abstract public class ChangeManagerWrapper extends ChangeManager
+{
+  /**
+   * Delegates the addComponentChange() implementation to the wrapped
+   * ChangeManager.
+   */
+  @Override
+  public void addComponentChange(
+    FacesContext context,
+    UIComponent component,
+    ComponentChange change)
+  {
+    getWrappedChangeManager().addComponentChange(context, component, change);
+  }
+
+  /**
+   * Delegates the addDocumentChange() implementation to the wrapped ChangeManager.
+   * 
+   * @see #addDocumentChangeWithOutcome(FacesContext, UIComponent, DocumnentChange)
+   */
+  @Override
+  @SuppressWarnings("deprecation")
+  public void addDocumentChange(
+    FacesContext context,
+    UIComponent component,
+    DocumentChange change)
+  {
+    getWrappedChangeManager().addDocumentChange(context, component, change);
+  }
+
+  /**
+   * Delegates the applyComponentChangesForCurrentView() implementation to 
+   * the wrapped ChangeManager.
+   */
+  @Override
+  public void applyComponentChangesForCurrentView(FacesContext context)
+  {
+    getWrappedChangeManager().applyComponentChangesForCurrentView(context);
+  }
+
+  /**
+   * Delegates the applyComponentChangesForSubtree() implementation to the 
+   * wrapped ChangeManager.
+   */
+  @Override
+  public void applyComponentChangesForSubtree(FacesContext context, NamingContainer root)
+  {
+    getWrappedChangeManager().applyComponentChangesForSubtree(context, root);
+  }
+
+  /**
+   * Delegates the applySimpleComponentChanges() implementation to the wrapped
+   * ChangeManager.
+   */
+  @Override
+  public void applySimpleComponentChanges(FacesContext context, UIComponent component)
+  {
+    getWrappedChangeManager().applySimpleComponentChanges(context, component);
+  }
+
+  /**
+   * Delegates the documentChangeApplied() implementation to the wrapped ChangeManager.
+   */
+  @Override
+  public NotificationOutcome documentChangeApplied(
+    FacesContext facesContext, 
+    UIComponent component, 
+    ComponentChange componentChange)
+  {
+    return getWrappedChangeManager().documentChangeApplied(facesContext, 
+                                                           component, 
+                                                           componentChange);
+  }
+
+  /**
+   * Delegates the addDocumentChangeWithOutcome() implementation to the wrapped ChangeManager.
+   */
+  @Override
+  public ChangeOutcome addDocumentChangeWithOutcome(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    DocumentChange change)
+  {
+    return getWrappedChangeManager().addDocumentChangeWithOutcome(facesContext, 
+                                                                  uiComponent, 
+                                                                  change);
+  }
+  
+  /**
+   * Delegates the replaceAttributeChangeIfPresent() implementation to the wrapped ChangeManager.
+   */
+  @Override
+  public AttributeComponentChange replaceAttributeChangeIfPresent(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    AttributeComponentChange attributeComponentChange)
+  {
+    return getWrappedChangeManager().replaceAttributeChangeIfPresent(facesContext,
+                                                                     uiComponent,
+                                                                     attributeComponentChange);
+  }
+
+  /**  
+   * Returns the wrapped ChangeManager.
+   */  
+  abstract protected ChangeManager getWrappedChangeManager();
+}

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java?rev=1483979&r1=1483978&r2=1483979&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java (original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/change/SessionChangeManager.java Fri May 17 20:33:01 2013
@@ -42,18 +42,15 @@ 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. 
+ * 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
@@ -120,6 +117,72 @@ public class SessionChangeManager extend
   }
 
   /**
+   * @inheritDoc
+   */
+  @Override
+  @SuppressWarnings("deprecation")
+  public void addDocumentChange(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    DocumentChange change)
+  {
+    addDocumentChangeWithOutcome(facesContext, uiComponent, change);
+  }
+  
+  /**
+   * @inheritDoc
+   */
+  @Override
+  public ChangeOutcome addDocumentChangeWithOutcome(
+    FacesContext facesContext,
+    UIComponent uiComponent,
+    DocumentChange change)
+  {
+    if (facesContext == null || uiComponent == null || change == null)
+      throw new IllegalArgumentException(_LOG.getMessage(
+        "CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL"));
+
+    return ChangeOutcome.CHANGE_NOT_APPLIED;
+  }
+  
+  /**
+   * In this implementation, if a simple attribute document change was applied, we will remove
+   *  previously added component change on the same component and attribute, if any.
+   */
+  @Override
+  public NotificationOutcome documentChangeApplied(
+    FacesContext facesContext, 
+    UIComponent component, 
+    ComponentChange componentChange)
+  {
+    NotificationOutcome outcome = super.documentChangeApplied(facesContext, 
+                                                              component, 
+                                                              componentChange);
+    
+    if (componentChange instanceof AttributeComponentChange)
+    {
+      AttributeComponentChange attributeChange = (AttributeComponentChange)componentChange;
+      // given that document change was applied, any previously added component change is redundant, 
+      // remove it
+      ComponentChange removedChange = _removeSimpleComponentChange(facesContext,
+                                                                   component,
+                                                                   attributeChange);
+      
+      if (removedChange != null)
+      {
+        if (_LOG.isFine())
+        {
+          _LOG.fine("REMOVED_DOC_CHANGE_BECAUSE_COMP_CHANGE_ADDED", new Object[] {attributeChange.getAttributeName(), component});
+        }
+        
+        outcome = NotificationOutcome.HANDLED;
+      }
+    }
+    
+    return outcome;
+  }
+  
+  /**
    * Adds a ComponentChange and registers against the supplied component.
    * Changes added thus live at Session scope.
    * Use applyComponentChangesForCurrentView() to apply these changes.
@@ -139,6 +202,7 @@ public class SessionChangeManager extend
     {
       _replaceAttributeChange(context,
                               targetComponent,
+                              ((AttributeComponentChange)componentChange).getAttributeName(),
                               (AttributeComponentChange)componentChange,
                               false); // replace no matter what
     }
@@ -155,7 +219,10 @@ public class SessionChangeManager extend
       String logicalScopedIdForTargetComponent = 
         ComponentUtils.getLogicalScopedIdForComponent(targetComponent, context.getViewRoot());
       
-      _insertComponentChange(changesForView, scopedIdForTargetComponent, logicalScopedIdForTargetComponent, componentChange);
+      _insertComponentChange(changesForView, 
+                             scopedIdForTargetComponent, 
+                             logicalScopedIdForTargetComponent, 
+                             componentChange);
       
       // dirty the key in the session for failover
       context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
@@ -171,7 +238,11 @@ public class SessionChangeManager extend
     UIComponent              component,
     AttributeComponentChange attributeComponentChange)
   {
-    return _replaceAttributeChange(context, component, attributeComponentChange, true);
+    return _replaceAttributeChange(context, 
+                                   component, 
+                                   attributeComponentChange.getAttributeName(), 
+                                   attributeComponentChange, 
+                                   true);
   }  
 
   /** 
@@ -184,19 +255,50 @@ public class SessionChangeManager extend
   }
 
   /**
-   *
+   * Removes (if any) the previously added simple component change for the supplied component and 
+   *  attribute 
+   *  
+   * @return The removed ComponentChange instance, or null if the ComponentChange was not removed
+   */
+  private ComponentChange _removeSimpleComponentChange(
+    FacesContext facesContext,
+    UIComponent component,
+    AttributeComponentChange componentChange)
+  {
+    String sessionKey = _getSessionKey(facesContext);
+    ChangesForView changesForView = _getChangesForView(facesContext, sessionKey, true);
+    String logicalScopedId = 
+      ComponentUtils.getLogicalScopedIdForComponent(component, facesContext.getViewRoot());
+
+    // first remove from the simple component changes structure that we maintained for convenience
+    changesForView.removeAttributeChange(logicalScopedId, componentChange);
+
+    // next, remove (replace with null) any attribute change for this attribute from the global 
+    //  changes list, handling the case where the component could have been moved after the 
+    //  attribute change was added
+    return _replaceAttributeChange(facesContext,
+                                   component,
+                                   componentChange.getAttributeName(),
+                                   null,
+                                   true);
+  }
+
+  /**
    * @param context
    * @param component
-   * @param attributeComponentChange
+   * @param attributeName The name of the attribute for which the attribute change is to be replaced
+   * @param attributeComponentChange The attributeChange that should now replace, If null, we just remove the old one 
+   *                                  and return
    * @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,
+    String                   attributeName,
     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(
@@ -208,18 +310,29 @@ public class SessionChangeManager extend
     String         sessionKey     = _getSessionKey(context);
     ChangesForView changesForView = _getChangesForView(context, sessionKey, true);
 
+    // remove the attribute change first if it existed
     AttributeComponentChange replaced = _extractAttributeChange(changesForView, 
                                                                 scopedIdForTargetComponent, 
-                                                                attributeComponentChange);
+                                                                attributeName);
     
-    // if found, we insert the new change instance
-    if (!onlyIfPresent || (replaced != null))
+    // if found, we insert the change instance we are asked to replace with
+    if ( attributeComponentChange != null && (!onlyIfPresent || (replaced != null)) )
     {
+      // a sanity check - the change better be for the same attribute
+      if ( !(attributeName.equals(attributeComponentChange.getAttributeName())) )
+      {
+        throw new IllegalArgumentException("The supplied attribute name does not match the " +
+                                           "attribute name in the supplied attribute change");
+      }
+      
       String logicalScopedIdForTargetComponent = ComponentUtils.getLogicalScopedIdForComponent(
                                                                               component,
                                                                               context.getViewRoot());
       
-      _insertComponentChange(changesForView, scopedIdForTargetComponent, logicalScopedIdForTargetComponent, attributeComponentChange);
+      _insertComponentChange(changesForView, 
+                             scopedIdForTargetComponent, 
+                             logicalScopedIdForTargetComponent, 
+                             attributeComponentChange);
 
       // dirty the key in the session for failover
       context.getExternalContext().getSessionMap().put(sessionKey, changesForView);
@@ -233,19 +346,20 @@ public class SessionChangeManager extend
    * - if not found, return null
    * - if found, remove and return the old change instance
    * 
+   * Note that this function will handle removing a component's attribute change even if the
+   * component was moved between naming containers after the attribute change was added
+   * 
    * @param changesForView
    * @param scopedIdForTargetComponent
-   * @param attributeChange
+   * @param attributeName The name of attribute for which the attribute change is to be extracted
    * @return the old change instance, null if not found
    */
   private AttributeComponentChange _extractAttributeChange(
-    ChangesForView           changesForView,
-    String                   scopedIdForTargetComponent,
-    AttributeComponentChange attributeChange)
+    ChangesForView changesForView,
+    String         scopedIdForTargetComponent,
+    String         attributeName)
   {
     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
@@ -446,7 +560,7 @@ public class SessionChangeManager extend
   /**
    * 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.
+   *  if this is the case, we are sure that there was no state restoration. 
    */
   private boolean _isStateRestored(FacesContext facesContext)
   {
@@ -568,7 +682,7 @@ public class SessionChangeManager extend
     
     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
@@ -635,7 +749,7 @@ public class SessionChangeManager extend
       // make sure that we don't add changes while getAttrChanges() is rebuilding the
       // per-component changes
       _componentChangesForView.add(qualifiedChange);
-        
+      
       ComponentChange componentChange = qualifiedChange.getComponentChange();
 
       if (componentChange instanceof AttributeComponentChange)
@@ -659,6 +773,24 @@ public class SessionChangeManager extend
         }
       }
     }
+    
+    /**
+     * Removes any previously stored attribute change for this component and attribute. Any moves 
+     *  later to an earlier addition of attribute change is taken care of, by means of removing such 
+     *  attribute change.
+     * @return The attribute change that was removed
+     */
+    protected AttributeComponentChange removeAttributeChange(
+      String                   logicalScopedId, 
+      AttributeComponentChange attributeChange)
+    {
+      // remove this change from the attribute changes map that gets used for applying simple 
+      //  component changes
+      return _removeAttributeChange(_attrChanges, 
+                                    _renameMap, 
+                                    logicalScopedId, 
+                                    attributeChange);
+    }
       
     /**
      * Returns the Iterator of rename changes that affect the current scoped id in ComponentChange order
@@ -720,54 +852,98 @@ public class SessionChangeManager extend
       // all phases including tag execution
       String scopedId = ComponentUtils.getLogicalScopedIdForComponent(component, context.getViewRoot());
       
-      ConcurrentMap<String, ComponentChange> componentChanges = _attrChanges.get(scopedId);
+      ConcurrentMap<String, AttributeComponentChange> attributeCmponentChanges = _attrChanges.get(scopedId);
       
-      if (componentChanges != null)
+      if (attributeCmponentChanges != null)
       {
-        for (ComponentChange change : componentChanges.values())
+        for (ComponentChange change : attributeCmponentChanges.values())
         {
           change.changeComponent(component);
         }
       }
     }
-
+    
     private void _updateAttributeChange(
-      ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> attrChanges,
-      ConcurrentMap<String, String>                                 renameMap,
-      QualifiedComponentChange                                      qAttrChange)
+      ConcurrentMap<String, ConcurrentMap<String, AttributeComponentChange>>  attrChanges,
+      ConcurrentMap<String, String>                                           renameMap,
+      QualifiedComponentChange                                                qAttrChange)
+    {
+      // get all the attribute changes for this component after considering possible moves.
+      //  We want to update, so create if necessary
+      ConcurrentMap<String, AttributeComponentChange> changesForComponent = 
+        _getAttributeChangesAfterHandlingMove(attrChanges, 
+                                              renameMap, 
+                                              qAttrChange.getTargetComponentLogicalScopedId(), 
+                                              true);
+      
+      AttributeComponentChange attrChange = (AttributeComponentChange)
+                                            qAttrChange.getComponentChange();
+      
+      // update the current AttributeComponentChange for this attribute
+      String attrName = attrChange.getAttributeName();
+      
+      changesForComponent.put(attrName, attrChange);
+    }
+
+    private AttributeComponentChange _removeAttributeChange(
+      ConcurrentMap<String, ConcurrentMap<String, AttributeComponentChange>> attrChanges,
+      ConcurrentMap<String, String>                                          renameMap,
+      String                                                                 logicalScopedId,
+      AttributeComponentChange                                               componentChange)
     {
-      // update the current attribute values for the scoped id
-      String currScopedId = qAttrChange.getTargetComponentLogicalScopedId();
+      // get all the attribute changes for this component after considering possible moves
+      ConcurrentMap<String, AttributeComponentChange> changesForComponent = 
+        _getAttributeChangesAfterHandlingMove(attrChanges, renameMap, logicalScopedId, false);
       
-      // apply any move rename
-      String originalScopedId = renameMap.get(currScopedId);
+      if (changesForComponent != null)
+      {
+        return changesForComponent.remove(componentChange.getAttributeName());
+      }
       
-      // we don't add rename mapping until a move, so if there is no entry, the origina
-      // value is good
+      return null;
+    }
+    
+    /**
+     * Gets the map of all the attribute changes for a component with the supplied logical scoped id.
+     * 
+     * @param attrChanges The map of attribute change collection per component keyed by the id
+     * @param renameMap The id renaming map for moved components
+     * @param logicalScopedId The logical scoped id of the component for which attribute changes are 
+     *                         needed
+     * @param createIfNecessary Create the attribute changes map if there are no attribute changes
+     *                           for the component with supplied logical scoped id.
+     * @return The map of attribute changes keyed by the attribute name
+     */
+    private ConcurrentMap<String, AttributeComponentChange> _getAttributeChangesAfterHandlingMove(
+      ConcurrentMap<String, ConcurrentMap<String, AttributeComponentChange>>  attrChanges,
+      ConcurrentMap<String, String>                                           renameMap,
+      String                                                                  logicalScopedId,
+      boolean                                                                 createIfNecessary)
+    {
+      // handle renames due to possible moves and get the original id
+      String originalScopedId = renameMap.get(logicalScopedId);
+      
+      // we don't add rename mapping until a move, so if there is no entry, the component did not
+      // move, the original value is good
       if (originalScopedId == null)
-        originalScopedId = currScopedId;
+      {
+        originalScopedId = logicalScopedId;
+      }
       
-      // get the map for this component, creating one if necessary
-      ConcurrentMap<String, ComponentChange> changesForComponent = 
+      // get the map of attribute changes for this component, creating one if necessary
+      ConcurrentMap<String, AttributeComponentChange> changesForComponent = 
                                                            attrChanges.get(originalScopedId);
       
       // if we haven't registered a Map yet, create one and register it
-      if (changesForComponent == null)
+      if (changesForComponent == null && createIfNecessary)
       {
         // =-= 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>();
+        changesForComponent = new ConcurrentHashMap<String, AttributeComponentChange>();
         attrChanges.put(originalScopedId, changesForComponent);
       }
       
-      AttributeComponentChange attrChange = (AttributeComponentChange)
-                                            qAttrChange.getComponentChange();
-      
-      // update the current AttributeComponentChange for this attribute
-      String attrName = attrChange.getAttributeName();
-      
-      changesForComponent.put(attrName, attrChange);
-      
+      return changesForComponent;
     }
     
     /**
@@ -798,7 +974,7 @@ public class SessionChangeManager extend
         renameMap.put(destinationScopedId, originalScodeId);
       }
     }
-
+    
     private void readObject(java.io.ObjectInputStream in) 
       throws IOException, ClassNotFoundException 
     {
@@ -839,15 +1015,14 @@ public class SessionChangeManager extend
       
       private static final long serialVersionUID = 1L;
     }
-
-
+             
     private final Queue<QualifiedComponentChange> _componentChangesForView;
     private final List<MoveChildComponentChange> _renameChanges;
     
     // map of original scopedIds to Map of attribute names and their new values.  This allows
     // us to apply all of attribute changes efficiently
-    private final ConcurrentMap<String, ConcurrentMap<String, ComponentChange>> _attrChanges = 
-      new ConcurrentHashMap<String, ConcurrentMap<String, ComponentChange>>();
+    private final ConcurrentMap<String, ConcurrentMap<String, AttributeComponentChange>> _attrChanges = 
+      new ConcurrentHashMap<String, ConcurrentMap<String, AttributeComponentChange>>();
     
     // 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

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/xrts/org/apache/myfaces/trinidad/resource/LoggerBundle.xrts
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/xrts/org/apache/myfaces/trinidad/resource/LoggerBundle.xrts?rev=1483979&r1=1483978&r2=1483979&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/xrts/org/apache/myfaces/trinidad/resource/LoggerBundle.xrts (original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/xrts/org/apache/myfaces/trinidad/resource/LoggerBundle.xrts Fri May 17 20:33:01 2013
@@ -201,6 +201,9 @@
  <!-- CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL -->
  <resource key="CANNOT_ADD_CHANGE_WITH_FACECONTEXT_OR_UICOMPONENT_OR_NULL">Cannot add a Change with either of FacesContext, UIComponent or Change being null.</resource>
  
+  <!-- REMOVED_DOC_CHANGE_BECAUSE_COMP_CHANGE_ADDED -->
+ <resource key="REMOVED_DOC_CHANGE_BECAUSE_COMP_CHANGE_ADDED">AttributeComponentChange for the attribute {1} on the component {2} was removed because an equivalent DocumentChange was added.</resource>
+ 
  <!-- CANNOT_CONSTRUCT_CHANGECOMPONENTPROXY_WITH_NULL_UICOMPONENT -->
  <resource key="CANNOT_CONSTRUCT_CHANGECOMPONENTPROXY_WITH_NULL_UICOMPONENT">Cannot construct an ChangeComponentProxy with null UIComponent.</resource>