myfaces-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bsulli...@apache.org
Subject svn commit: r919499 - in /myfaces/trinidad/trunk: trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/
Date Fri, 05 Mar 2010 17:04:43 GMT
Author: bsullivan
Date: Fri Mar  5 17:04:43 2010
New Revision: 919499

URL: http://svn.apache.org/viewvc?rev=919499&view=rev
Log:
TRINIDAD-1739 ComponentReference doesn't work with bindings and should be more thread-safe

The solution to 1) is to internally use two different implementations--one for the case where
the component meets the current requirements, the second to handle the case where the component
has no id or isn't in the component tree yet. In this case, we defer calculating the scoped
id until all call that requires a scoped id--getComponent(), hashCode() and equals(). At this
point, presumably the component is in the tree and if it isn't we throw an IllegalStateException
(instead of the previous IllegalArgumentException). There are two more parts to this problem--there
is no guarantee that the deferred ComponentReference will actually be called at all, but the
deferred instance is holding onto a Component and we don't want to do so across requests,
so we maintain a list of all of the deferred ComponentRefererences and call a new method--ensureInstantiation()
on all of them at the end of the request from the GlobalConfiguratorImpl. The other trick
is that we only want to deserial
 ize stable component references, so we now use a serialization proxy instead of default serialization.

The thread-safety solution is to make judicious use of thread-safe references to mutable data
internally and guarantee that getComponent() can be called on an instantiated ComponentReference
as long as the call is made from a Thread with a FacesContext.

Modified:
    myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ComponentReference.java
    myfaces/trinidad/trunk/trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ComponentReferenceTest.java
    myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java

Modified: myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ComponentReference.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ComponentReference.java?rev=919499&r1=919498&r2=919499&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ComponentReference.java
(original)
+++ myfaces/trinidad/trunk/trinidad-api/src/main/java/org/apache/myfaces/trinidad/util/ComponentReference.java
Fri Mar  5 17:04:43 2010
@@ -18,8 +18,11 @@
  */
 package org.apache.myfaces.trinidad.util;
 
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -32,132 +35,271 @@
 /**
  * A utility to store a reference to an <code>UIComponent</code>. Application
developers
  * should use this tool if they need to have a reference to an instance of the
- * <code>UIComponent</code> class in <code>managed beans</code> that
are <b>session scoped</b>.
+ * <code>UIComponent</code> class in <code>managed beans</code> that
are longer than <b>requested scoped</b>
+ * --for example Session and Application Scoped.  The reference will return the UIComponent,
if any, with
+ * the same scoped id as the Component used to create the reference, in the current UIViewRoot.
  * 
  * Use <code>newUIComponentReference()</code> to create a <code>ComponentReference</code>
and
  * use the <code>getComponent()</code> to look up the referenced <code>UIComponent</code>.
+ *  
+ * For example, a current weather application might have have a session scoped weatehrBean
+ * containing the current list of locations to report the weather on and support using a
+ * selectMany component to remove the locations:
  * 
+ * <pre>
+ * <tr:selectManyCheckbox label="Locations" id="smc1" valuePassThru="true"
+ *                        binding="#{weatherBean.locationsSelectManyComponent}"
+ *                       value="#{weatherBean.locationsToRemove}">
+ *    <f:selectItems value="#{weatherBean.locationSelectItems}" id="si1"/>
+ * </tr:selectManyCheckbox>
+ * <tr:commandButton id="deleteCB" text="Remove Locations"
+ *                  actionListener="#{weatherBean.removeLocationListener}">
+ * </tr:commandButton>
+ * </pre>
+ * The weatherBean might looks like this:
+ * <pre>
+ * public class WeatherBean implements Serializable
+ * {
+ *   public void setLocationsToRemove(UIXSelectMany locationsToRemove)
+ *   {
+ *     _locationsToRemove = locationsToRemove;
+ *   }
+ *   
+ *   public UIXSelectMany getLocationsToRemove()
+ *   {
+ *     return _locationsToRemove;
+ *   }
+ *   
+ *   public void removeLocationListener(ActionEvent actionEvent)
+ *   {
+ *     ... code calling getLocationsToRemove() to get the UIXSelectMany ...
+ *   }
+ *
+ *   private UIXSelectMany _locationsToRemove
+ * }
+ * </pre>
+ * This code has several problems:
+ * <ol>
+ *   <li>Since UIComponents aren't Serializable, the class will fail serialization
during fail-over
+ *   when default Serialization attempts to serialize _locationsToRemove.</li>
+ *   <li>If the user opens two windows on this page, only the last window rendered
have the
+ *   correct UIXSelectMany instance.  If the remove locations button is pressed on the first
+ *   window after rendering the second window, the wrong UIXSelectMany instance will be used.</li>
+ *   <li>Since UIComponents aren't thread-safe, the above case could also result in
bizare
+ *   behavior if requests from both windows were being processed by the application server
at the
+ *   same time.</li>
+ *   <li>If the Trinidad view state token cache isn't used, or if the user navigates
to this page
+ *   using the backbutton, a new UIXSelectMany instance will be created, which also won't
match
+ *   the instance we are holding onto.</li>
+ *   <li>If we don't clear the UIXSelectMany instance when we navigate off of this
page, we will
+ *   continue to pin the page's UIComponent tree in memory for the lifetime of the Session.
+ *   </li>
+ * </ol>
+ * Rewritten using ComponentReference, the weatherBean might looks like this:
+ * <pre>
+ * public class WeatherBean implements Serializable
+ * {
+ *   public void setLocationsToRemove(UIXSelectMany locationsToRemove)
+ *   {
+ *     _locationsToRemoveRef = UIComponentReference.newUIComponentReference(locationsToRemove);
+ *   }
+ *   
+ *   public UIXSelectMany getLocationsToRemove()
+ *   {
+ *     return _locationsToRemoveRef.getComponent();
+ *   }
+ *   
+ *   public void removeLocationListener(ActionEvent actionEvent)
+ *   {
+ *     ... code calling getLocationsToRemove() to get the UIXSelectMany ...
+ *   }
+ *
+ *   private UIComponentReference<UIXSelectMany> _locationsToRemoveRef
+ * }
+ * </pre>
+ * The above code saves a reference to the component passed to the managed bean and then
+ * retrieves the correct instance given the current UIViewRoot for this request whenever
+ * <code>getLocationsToRemove()</code> is called.
  * <p><b>Please note:</b>
  * <ul>
- * <li>This class is <b>not</b> thread-safe, since it depends on <code>UIComponent</code>
APIs.
- * <li>The passed in <code>UIComponent</code> is <b>required</b>
to have an <code>ID</code>
- * <li>The reference will break if the <code>UIComponent</code> is moved
between <code>NamingContainer</code>s <b>or</b>
- * if any of the ancestor <code>NamingContainer</code>s have their IDs changed.
- * <li>The refrence is persistable. <b>However</b> <code>UIComponent</code>s
are not <code>Serializable</code> and therefore
- * can not be used at any scope longer than request.
- * 
+ * <li>This class is <b>not completely</b> thread-safe, since it depends
on <code>UIComponent</code>
+ * APIs, however the class is safe to use as long as either of the following is true
+ * <ol>
+ * <li>The component passed to <code>newUIComponentReference</code> has
an id and is in the
+ * component hierarchy when newUIComponentReference is called and all subsequent calls to
+ * <code>getComponent</code> are made from Threads with a valid FacesContext
+ * </li>
+ * <li>The first call to <code>getComponent</code> is on the same Thread
that
+ * <code>newUIComponentReference</code> was called on</li>
+ * </ol>
+ * </li>
+ * <li>The passed in <code>UIComponent</code> is <b>required</b>
to have an <code>ID</code></li>
+ * <li>The reference will break if the <code>UIComponent</code> is moved
between
+ * <code>NamingContainer</code>s <b>or</b>
+ * if any of the ancestor <code>NamingContainer</code>s have their IDs changed.</li>
+ * <li>The reference is persistable. <b>However</b> <code>UIComponent</code>s
are not
+ * <code>Serializable</code> and therefore can not be used at any scope longer
than request.</li>
  * </ul>
  * 
  * @see ComponentReference#newUIComponentReference(UIComponent)
  * @see ComponentReference#getComponent()
  */
-public final class ComponentReference<T extends UIComponent> implements Serializable
+public abstract class ComponentReference<T extends UIComponent> implements Serializable
 {
-  /**
-   * Private constructor, used by <code>ComponentReference.newUIComponentReference</code>

-   * @param uicomponent the <code>UIComponent</code> we want to store the path
for
-   */
-  private ComponentReference(T uicomponent)
+  // don't allow other subclasses
+  private ComponentReference(List<Object> componentPath)
   {
-    // create the "component path" to remember the position of the uicomponent and begin
-    // the work to figureout the scopedID
-    _scopedId = _createComponentPathAndScopedId(uicomponent, null);
+    _componentPath = componentPath;
   }
 
   /**
-   * Factory method to create an instance of the <code>ComponentReference</code>
class, which wraps the given
-   * <code>UIComponent</code>. The component must be in the component tree when
this method
-   * is called and that we will throw an <code>IllegalArgumentException</code>
if it is not.
+   * Factory method to create an instance of the <code>ComponentReference</code>
class, which
+   * returns a Serializable and often thread-safe reference to a 
+   * <code>UIComponent</code>.
    * 
-   * @param uicomponent the <code>UIComponent</code> to wrap.
-   * @throws IllegalArgumentException if component is not in the component tree
-   * @throws IllegalArgumentException if component does <b>not</b> have an <code>Id</code>
-   * @return <code>ComponentReference</code> that wrap the given component
+   * @param component the <code>UIComponent</code> to create a reference to.
+   * @return <code>ComponentReference</code> the reference to the component
+   * @throws NullPointerException if component is <code>null</code>
    */
-  public static <T extends UIComponent> ComponentReference<T> newUIComponentReference(T
uicomponent)
+  public static <T extends UIComponent> ComponentReference<T> newUIComponentReference(T
component)
   {
-    return new ComponentReference<T>(uicomponent);
-  }
+    // store the id of the component as a transient field since we can grab it from the scoped
id
+    // but want it available to validate the component we found from the path
+    String compId = component.getId();
 
+    // if the component is in the hierarchy, the topmost component will be the UIViewRoot
+    if ((compId != null) && (getUIViewRoot(component) != null))
+    {
+      // component has an id and is in the hierarachy, so we can use a stable reference
+      String scopedId = calculateScopedId(component, compId);
+      
+      return new StableComponentReference(scopedId, compId, calculateComponentPath(component));
+    }
+    else
+    {
+      // Oh well, deferred reference it is
+      ComponentReference<T> reference = new DeferredComponentReference<T>(component);
+      
+      // Add to the list of Referernces that may need initialization
+      _addToEnsureInitializationList(reference);
+      
+      return reference;
+    }
+  }
+  
   /**
    * This method will use a calculated "component path" to walk down to the <code>UIComponent</code>
    * that is referenced by this class. If the component can not be found, the <code>getComponent()</code>
    * will return <code>null</code>. 
    * 
    * @return the referenced <code>UIComponent</code> or <code>null</code>
if it can not be found.
+   * @throws IllegalStateException if the component used to create the
+   * ComponentReference is not in the component tree or does <b>not</b> have
an <code>Id</code>
    * @see ComponentReference#newUIComponentReference(UIComponent)
    */
-  @SuppressWarnings("unchecked")
-  public T getComponent()
+   @SuppressWarnings("unchecked")
+   public final T getComponent()
+   {
+     // get the scopedId, calculating it if necessary
+     String scopedId = getScopedId();
+         
+     UIComponent foundComponent = null;
+
+     // In order to find the component with its
+     // calculated path, we need to start at the ViewRoot;
+     UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
+
+     List<Object> componentPath = _componentPath;
+     
+     if (componentPath != null) 
+     {
+       // Walk down the component tree, to the component we are looking for.
+       // We start at the ViewRoot and use the previous calculated "component path"
+       foundComponent = _walkPathToComponent(root, componentPath);
+     }
+
+     // Check if we really found it with the previously created "component path"
+     if (foundComponent == null || (!getComponentId().equals(foundComponent.getId())))
+     {
+       // OK, we were not luck with the calculated "component path", let's
+       // see if we can find it by using the "scoped ID" and the regular 
+       // findComponent();
+       foundComponent = root.findComponent(scopedId);
+
+       // was the regular findComponent() successful ?
+       if (foundComponent != null)
+       {        
+         // OK, now let's rebuild the path
+         _componentPath = calculateComponentPath(foundComponent);
+       }
+     }
+
+     return (T)foundComponent;
+   }
+
+   /**
+    * Called by the framework to ensure that deferred ComponentReferences are completely
+    * initialized before the UIComponent that the ComponentReference is associated with
+    * is not longer valid.
+    * @throws IllegalStateException if ComponentReference isn't already initialized and 
+    * the component used to create the
+    * ComponentReference is not in the component tree or does <b>not</b> have
an <code>Id</code>
+    */
+   public abstract void ensureInitialization();
+    
+  /**
+   * ComponentRefs are required to test for equivalence by the equivalence of their scoped
ids
+   * @param o
+   * @return
+   */
+  @Override
+  public final boolean equals(Object o)
   {
-    UIComponent foundComponent = null;
-
-    // In order to find the component with its
-    // calculated path, we need to start at the ViewRoot;
-    UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
-
-
-    if (_componentPath != null) 
+    if (o == this)
     {
-      // Walk down the component tree, to the component we are looking for.
-      // We start at the ViewRoot and use the previous calculated "component path"
-      foundComponent = _walkPathToComponent(root, _componentPath);
+      return true;
     }
-
-    // Check if we really found it with the previously created "component path"
-    if (foundComponent == null || (!_componentId.equals(foundComponent.getId())))
+    else if (o instanceof ComponentReference)
     {
-      // OK, we were not luck with the calculated "component path", let's
-      // see if we can find it by using the "scoped ID" and the regular 
-      // findComponent();
-      foundComponent = root.findComponent(_scopedId);
-
-      // was the regular findComponent() successful ?
-      if (foundComponent != null)
-      {        
-        // OK, now let's rebuild the path
-        _createComponentPathAndScopedId(foundComponent, _scopedId);
-      }
+      return getScopedId().equals(((ComponentReference)o).getScopedId());
+    }
+    else
+    {
+      return false;
     }
-
-    return (T) foundComponent;
   }
-
-  /**
-   * Utility to (re) create the component Id from the <code>_scopedId</code>.
-   * 
-   * @return the extracted <code>UIComponentId</code>
-   */
-  private String _calculateComponentIdFromScopedId(String scopedId)
+ 
+   /**
+    * ComponentRefs must use the hash code of their scoped id as their hash code
+    * @return
+    */
+  @Override
+  public final int hashCode()
   {
-    // String.substring() is optimized to return this if the entire string
-    // is the substring so that no one tries to optimize it later
-    return scopedId.substring(scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR)+1);
+    return getScopedId().hashCode();
   }
-
+  
+  @Override
+  public final String toString()
+  {
+    return super.toString() + ":" + getScopedId();
+  }
+  
   /**
-   * Transform the <code>scopedIdList</code> of "important" component IDs to

-   * generate the <code>scopedID</code> for the referenced <code>UIComponent</code>.
-   * 
-   * Uses the <code>scopedIdLength</code>
+   * Returns the scoped id for this ComponentReference
+   * @return
    */
-  private String _createScopedId(int scopedIdLength, List<String> scopedIdList)
-  {
-    StringBuilder builder = new StringBuilder(scopedIdLength);
-
-    for (int i = scopedIdList.size()-1; i >= 0 ; i--)
-    {
-      builder.append(scopedIdList.get(i));
+  protected abstract String getScopedId();
 
-      // if there are more IDs in the list, we need to append a colon
-      if (i > 0)
-      {
-        builder.append(NamingContainer.SEPARATOR_CHAR);
-      }
-    }
+  /**
+   * Returns the id of the Component that this ComponentReference points to
+   * @return
+   */
+  protected abstract String getComponentId();
 
-    // store the (final) scopedId
-    return builder.toString();
+  protected final void setComponentPath(List<Object> componentPath)
+  {
+    _componentPath = componentPath;
   }
 
   /**
@@ -171,53 +313,15 @@
    * To calculate the <code>scopedID</code> we add the ID of every <code>NamingContainer</code>
that we hit,
    * while walking up the component tree, to the <code>scopedIdList</code>. 
    * 
-   * @param uicomponent The <code>UIComponent</code> for this current iteration
-   * @param scopedId , or null if neeed to build it
+   * @param component The <code>UIComponent</code> for this current iteration
    * 
-   * @see ComponentReference#newUIComponentReference()
+   * @see #newUIComponentReference
    */
-  private String _createComponentPathAndScopedId(UIComponent component, String scopedId)
+  protected static List<Object> calculateComponentPath(UIComponent component)
   {
     // setUp of list that stores information about the FACET name or the COMPONENT index
     List<Object> componentHierarchyList = new ArrayList<Object>();
 
-    // setUp of list that contains the IDs for all NamingContainers
-    // on the way up to the <code>UIViewRoot</code>
-    List<String> scopedIdList = null;
-
-    // extract the component Id and
-    // start to calculate its length... IF needed...
-    boolean needToCalculateScopedId = (scopedId == null);
-
-    int scopedIdLength = 0;
-    if (scopedId == null)
-    {
-      // store the id of the component as a transient field since we can grab it from the
scoped id
-      // but want it available to validate the component we found from the path
-      String currCompId = component.getId();
-
-      if (currCompId == null)
-      {
-        //TODO i18n
-        throw new IllegalArgumentException("The UIComponent needs to have an ID");
-      }
-      else
-      {
-        _componentId = currCompId;
-      }
-
-      scopedIdList = new ArrayList<String>();
-      scopedIdLength += _componentId.length();
-      // add the ID of the given UIComponent to (the beginning of) the list
-      scopedIdList.add(_componentId);
-    }
-    else
-    {
-      // extract componentId from scopedId
-      _componentId = _calculateComponentIdFromScopedId(scopedId);
-    }
-
-
     // stash the component and parent , for the loop
     UIComponent currComponent = component;
     UIComponent currParent = currComponent.getParent();
@@ -225,28 +329,13 @@
     // enter the loop, if there is a parent for the current component
     while(currParent != null)
     {
-
-      // When current parent is a NamingContainer we want to scope the ID
-      // but ONLY if that has not been done (needToCalculateScopedId:true)
-      if (needToCalculateScopedId && (currParent instanceof NamingContainer))
-      {
-        // extract the parent-component Id and
-        // continue to calculate the final length of the scopedId
-        // the "+1" takes the SEPARATOR_CHAR into account
-        String namingContainerId = currParent.getId();
-        scopedIdLength += namingContainerId.length() + 1;  
-        // as we hit a NamingContainer we add its ID to
-        // the beginning of the "scopedId" list
-        scopedIdList.add(namingContainerId);
-      }
-      // end of scopedID business, for this call;
-
-
+      int childIndex = currParent.getChildren().indexOf(currComponent);
+      
       // is the given component a child of the parent?
-      if (currParent.getChildren().contains(currComponent))
+      if (childIndex != -1)
       {          
         // if so, add the INDEX (type: int) at the beginning of the list
-        componentHierarchyList.add(currParent.getChildren().indexOf(currComponent));
+        componentHierarchyList.add(childIndex);
       }
       else
       {
@@ -274,26 +363,118 @@
 
     // done with the loop as >currComponent< has no own parent. Which
     // means we must talk to <code>UIViewRoot</code> here.
-    // Otherwise the component is not connected to the tree,
-    // so we thrown an IllegalArgumentException
+    // Otherwise the component is not connected to the tree, but we should have already checked
this
+    // before calling this function
     if (!(currComponent instanceof UIViewRoot))
+      throw new IllegalStateException(
+                   "The component " + component + " is NOT connected to the component tree");
+  
+    return componentHierarchyList;
+  }
+  
+  protected static String calculateScopedId(
+    UIComponent component,
+    String      componentId)
+  {
+    if (componentId == null)
+      throw new IllegalStateException("Can't create a ComponentReference for component "
+
+                                      component +
+                                      " no id");    
+    int scopedIdLength = componentId.length();
+  
+    List<String> scopedIdList = new ArrayList<String>();
+   
+    // determine how many characters we need to store the scopedId.  We skip the component
itself,
+    // because we have already accounted for its id
+    UIComponent currAncestor = component.getParent();
+    
+    while (currAncestor != null)
     {
-      //TODO i18n
-      throw new IllegalArgumentException("The component " + component + " is NOT connected
to the component tree");
+      // add the sizes of all of the NamingContainer ancestors, plus 1 for each NamingContainer
separator
+      if (currAncestor instanceof NamingContainer)
+      {
+        String currId = currAncestor.getId();
+        scopedIdLength += currId.length() + 1;
+      
+        // add the NamingContainer to the list of NamingContainers
+        scopedIdList.add(currId);
+      }
+      
+      currAncestor = currAncestor.getParent();
     }
+    
+    // now append all of the NamingContaintes
+    return _createScopedId(scopedIdLength, scopedIdList, componentId);
+  }
+
+  private Object writeReplace()
+  {
+    // Only use the proxy when Serializing
+    return new SerializationProxy(getScopedId());
+  }
+  
+  private void readObject(@SuppressWarnings("unused") ObjectInputStream stream) throws InvalidObjectException
+  {
+    // We can't be deserialized directly
+    throw new InvalidObjectException("Proxy required");
+  } 
 
-    // once we are done with loop to the UIViewRoot, the "componentHierarchyList" as our
"component path"
-    _componentPath = componentHierarchyList;
+  /**
+   * Add a reference to the list of References that may need initialization later
+   * @param reference
+   */
+  private static void _addToEnsureInitializationList(ComponentReference<?> reference)
+  {
+    Map<String, Object> requestMap = 
+                            FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
+    
+    Collection<ComponentReference<?>> initializeList = (Collection<ComponentReference<?>>)
+                                             requestMap.get(_FINISH_INITIALIZATION_LIST_KEY);
     
-    // trigger the generation of the final scopedID
-    // OR return NULL (as we already have the scopedId
-    // when "needToCalculateScopedId" is false...
-    if (needToCalculateScopedId)
+    if (initializeList == null)
     {
-      scopedId = _createScopedId(scopedIdLength, scopedIdList);
+      initializeList = new ArrayList<ComponentReference<?>>();
+      requestMap.put(_FINISH_INITIALIZATION_LIST_KEY, initializeList);
     }
+    
+    initializeList.add(reference);
+  }
 
-    return scopedId;
+  /**
+   * Transform the <code>scopedIdList</code> of "important" component IDs to

+   * generate the <code>scopedID</code> for the referenced <code>UIComponent</code>.
+   * 
+   * Uses the <code>scopedIdLength</code>
+   */
+  private static String _createScopedId(int scopedIdLength, List<String> scopedIdList,
String componentId)
+  {
+    StringBuilder builder = new StringBuilder(scopedIdLength);
+
+    for (int i = scopedIdList.size() - 1; i >= 0 ; i--)
+    {
+      builder.append(scopedIdList.get(i));
+      builder.append(NamingContainer.SEPARATOR_CHAR);
+    }
+
+    builder.append(componentId);
+    
+    // store the (final) scopedId
+    return builder.toString();
+  }
+  
+  protected static UIViewRoot getUIViewRoot(UIComponent component)
+  {
+    // stash the component and parent , for the loop
+    UIComponent currComponent = component;
+    UIComponent currParent = currComponent.getParent();
+
+    while(currParent != null)
+    {
+      currComponent = currParent;
+      currParent = currParent.getParent();
+    }
+    
+    return (currComponent instanceof UIViewRoot) ? (UIViewRoot)currComponent : null;
   }
 
   /**
@@ -301,16 +482,16 @@
    * uses the <code>hierarchyInformationList</code> to check if the it needs
to
    * walk into a FACET or an INDEX of the component.
    * 
-   * @see ComponentReference#_createComponentPathAndScopedId(UIComponent)
+   * @see ComponentReference#calculateComponentPath(UIComponent)
    */
-  private UIComponent _walkPathToComponent(UIViewRoot root, List<Object> hierarchyInformationList)
+  private UIComponent _walkPathToComponent(UIViewRoot root, List<Object> componentPath)
   {
     UIComponent currFound = root;
 
     // iterate backwards since we appending the items starting from the component
-    for (int i = hierarchyInformationList.size() - 1; i >= 0 ; i--)
+    for (int i = componentPath.size() - 1; i >= 0 ; i--)
     {
-      Object location = hierarchyInformationList.get(i);
+      Object location = componentPath.get(i);
 
       // integer means we need to get the kid at INDEX obj
       // but let's not try to lookup from a component with
@@ -347,9 +528,152 @@
     }
     return currFound;
   }
+  
+  /**
+   * ComponentReference where the scopedId is calculatable at creation time
+   */
+  private static final class StableComponentReference extends ComponentReference
+  {
+    private StableComponentReference(String scopedId)
+    {      
+      this(scopedId,
+           // String.substring() is optimized to return this if the entire string
+           // is the substring, so no further optimization is necessary
+           scopedId.substring(scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR)+1),
+           null);
+    }
+ 
+    private StableComponentReference(
+      String scopedId,
+      String componentId,
+      List<Object> componentPath)
+    {
+      super(componentPath);
+
+      if (scopedId == null)
+        throw new NullPointerException();
+      
+      _scopedId = scopedId;
+      _componentId = componentId;
+    }
+    
+    public void ensureInitialization()
+    {
+      // do nothing--stable references are always fully initialized
+    }
+
+    protected String getScopedId()
+    {
+      return _scopedId;
+    }
+
+    protected String getComponentId()
+    {
+      return _componentId;
+    }
+ 
+    private final String _componentId;
+    private final String _scopedId;
+
+    private static final long serialVersionUID = 0L;
+  }
+  
+  /**
+   * ComponentReference where the component isn't ready to have its ComponentReference calculated
at
+   * creation time.  Instead we wait until getComponent() is called, or the ComponentReference
is
+   * Serialized.
+   */
+  private static final class DeferredComponentReference<T extends UIComponent> extends
ComponentReference
+  {
+    /**
+     * Private constructor, used by <code>ComponentReference.newUIComponentReference</code>

+     * @param component the <code>UIComponent</code> we want to store the path
for
+     */
+    private DeferredComponentReference(T component)
+    {
+      super(null);
+      
+      // temporarily store away the component
+      _component = component;
+    }
+
+    public void ensureInitialization()
+    {
+      // getScopedId() ensures we are initialized
+      getScopedId();
+    }
+
+    protected String getScopedId()
+    {
+      String scopedId = _scopedId;
+      
+      // we have no scopedId, so calculate the scopedId and finish initializing
+      // the DeferredComponentReference
+      if (scopedId == null)
+      {
+        UIComponent component = _component;
+        
+        // need to check that component isn't null because of possible race condition if
this
+        // method is called from different threads.  In that case, scopedId will have been
filled
+        // in, so we can return it
+        if (component != null)
+        {
+          String componentId = component.getId();
+                    
+          scopedId = calculateScopedId(component, componentId);
+          _scopedId = scopedId;
+          _componentId = componentId;
+          
+          // store away our component path while we can efficiently calculate it
+          setComponentPath(calculateComponentPath(component));
+          
+          _component = null;
+        }
+        else
+        {
+          scopedId = _scopedId;
+        }
+      }
+      
+      return scopedId;
+    }
+
+    protected String getComponentId()
+    {
+      return _componentId;
+    }
+    
+    private transient T _component;
+    private transient volatile String _componentId;
+    private volatile String _scopedId;
+
+    private static final long serialVersionUID = 0L;
+  }
+
+  /**
+   * Proxy class for serializing ComponentReferences.  The Serialized for is simply the scopedId
+   */
+  private static final class SerializationProxy implements Serializable
+  {
+    SerializationProxy(String scopedId)
+    {
+      _scopedId = scopedId;
+    }
+
+    private Object readResolve()
+    {
+      return new StableComponentReference(_scopedId);
+    }
+      
+    private final String _scopedId;
+
+    private static final long serialVersionUID = 0L;
+  }
+
+  private transient volatile List<Object> _componentPath;
 
-  private transient List<Object> _componentPath;
-  private transient String _componentId;
-  private final String _scopedId;
+  private static final String _FINISH_INITIALIZATION_LIST_KEY = ComponentReference.class.getName()
+
+                                                                "#FINISH_INITIALIZATION";
+  
   private static final long serialVersionUID = -6803949269368863899L;
 }
\ No newline at end of file

Modified: myfaces/trinidad/trunk/trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ComponentReferenceTest.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ComponentReferenceTest.java?rev=919499&r1=919498&r2=919499&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ComponentReferenceTest.java
(original)
+++ myfaces/trinidad/trunk/trinidad-api/src/test/java/org/apache/myfaces/trinidad/util/ComponentReferenceTest.java
Fri Mar  5 17:04:43 2010
@@ -217,14 +217,17 @@
     // build the Tree...
     root.getChildren().add(input1);
 
+    ComponentReference ref = ComponentReference.newUIComponentReference(input1);
+
     // Get the ComponentReference util
     try
     {
-      ComponentReference.newUIComponentReference(input1);
+      ref.getComponent();
+      
       // find the component...
-      fail("IllegalArgumentException expected");
+      fail("IllegalStateException expected");
     }
-    catch (Exception e)
+    catch (IllegalStateException e)
     {
       // suppress it - this is as expected
     }
@@ -294,6 +297,38 @@
     assertNull(referencedComp);
   }
 
+  public void testDeferredMovingInsideNamingContainer()
+  {
+    UIViewRoot root = facesContext.getViewRoot();
+    root.setId("root");
+    UINamingContainer nc1 = new UINamingContainer(); nc1.setId("nc1");
+    UINamingContainer nc2 = new UINamingContainer(); nc2.setId("nc2");
+    UINamingContainer nc3 = new UINamingContainer(); nc3.setId("nc3");
+
+    // almost build the tree
+    nc1.getChildren().add(nc2);
+    nc2.getChildren().add(nc3);
+
+    // Get the ComponentReference util, this will be a deferred component reference since
the
+    // component wasn't attached
+    ComponentReference<UINamingContainer> uiRef = ComponentReference.newUIComponentReference(nc3);
+
+    // now finish building the component tree
+    root.getChildren().add(nc1);
+
+    // find the component...
+    UINamingContainer referencedComp = uiRef.getComponent();
+    assertEquals(nc3, referencedComp);
+
+    // let's move the NC3 component one level up;
+    nc2.getChildren().remove(nc3);
+    nc1.getChildren().add(nc3);
+
+    // and we can not find the component...
+    referencedComp = uiRef.getComponent();
+    assertNull(referencedComp);
+  }
+
   public void testComponentNotInTree()
   {
     UINamingContainer nc1 = new UINamingContainer(); nc1.setId("nc1");
@@ -305,13 +340,16 @@
     nc2.getChildren().add(nc3);
 
     // Get the ComponentReference util
+    ComponentReference ref = ComponentReference.newUIComponentReference(nc3);
+
     try
     {
-      ComponentReference.newUIComponentReference(nc3);
       // find the component...
-      fail("IllegalArgumentException expected");
+      ref.getComponent();
+      
+      fail("IllegalStateException expected");
     }
-    catch (Exception e)
+    catch (IllegalStateException e)
     {
       // suppress it - this is as expected
     }

Modified: myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java
URL: http://svn.apache.org/viewvc/myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java?rev=919499&r1=919498&r2=919499&view=diff
==============================================================================
--- myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java
(original)
+++ myfaces/trinidad/trunk/trinidad-impl/src/main/java/org/apache/myfaces/trinidadinternal/config/GlobalConfiguratorImpl.java
Fri Mar  5 17:04:43 2010
@@ -19,6 +19,8 @@
 
 package org.apache.myfaces.trinidadinternal.config;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +29,8 @@
 
 import javax.faces.context.ExternalContext;
 
+import javax.faces.context.FacesContext;
+
 import javax.servlet.ServletRequest;
 
 import javax.servlet.ServletRequestWrapper;
@@ -40,6 +44,7 @@
 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
 import org.apache.myfaces.trinidad.skin.SkinFactory;
 import org.apache.myfaces.trinidad.util.ClassLoaderUtils;
+import org.apache.myfaces.trinidad.util.ComponentReference;
 import org.apache.myfaces.trinidadinternal.context.RequestContextFactoryImpl;
 import org.apache.myfaces.trinidadinternal.context.external.ServletCookieMap;
 import org.apache.myfaces.trinidadinternal.context.external.ServletRequestHeaderMap;
@@ -409,7 +414,6 @@
       }
       finally
       {
-
         //Do cleanup of anything which may have use the thread local manager durring
         //init.
         _releaseManagedThreadLocals();
@@ -480,12 +484,19 @@
     RequestContext context = RequestContext.getCurrentInstance();
     if (context != null)
     {
+      // ensure that any deferred ComponentReferences are initialized
+      _finishComponentReferenceInitialization(ec);
+
       context.release();
       _releaseManagedThreadLocals();
+      
       assert RequestContext.getCurrentInstance() == null;
     }
   }
 
+  /**
+   * Ensure that any ThreadLocals initialized during this request are cleared
+   */
   private void _releaseManagedThreadLocals()
   {
     ThreadLocalResetter resetter = _threadResetter.get();
@@ -495,6 +506,29 @@
       resetter.__removeThreadLocals();
     }
   }
+  
+  /**
+   * Ensure that all DeferredComponentReferences are fully initialized before the
+   * request completes
+   */
+  private void _finishComponentReferenceInitialization(ExternalContext ec)
+  {
+    Map<String, Object> requestMap = ec.getRequestMap();
+    
+    Collection<ComponentReference<?>> initializeList = (Collection<ComponentReference<?>>)
+                                             requestMap.get(_FINISH_INITIALIZATION_LIST_KEY);
+    
+    if ((initializeList != null) && !initializeList.isEmpty())
+    {
+      for (ComponentReference<?> reference : initializeList)
+      {
+        reference.ensureInitialization();
+      }
+      
+      // we've initialized everything, so we're done
+      initializeList.clear();
+    }
+  }
 
   private void _endConfiguratorServiceRequest(final ExternalContext ec)
   {
@@ -738,6 +772,10 @@
     static private String _TEST_PARAM = TestRequest.class.getName() + ".TEST_PARAM";
   }
 
+  // skanky duplication of key from ComponentReference Class
+  private static final String _FINISH_INITIALIZATION_LIST_KEY = ComponentReference.class.getName()
+
+                                                                "#FINISH_INITIALIZATION";
+
   // hacky reference to the ThreadLocalResetter used to clean up request-scoped
   // ThreadLocals
   private AtomicReference<ThreadLocalResetter> _threadResetter =



Mime
View raw message