View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.util;
20  
21  import java.io.InvalidObjectException;
22  import java.io.ObjectInputStream;
23  import java.io.ObjectStreamException;
24  import java.io.Serializable;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import javax.faces.component.NamingContainer;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UIViewRoot;
34  import javax.faces.context.FacesContext;
35  
36  /**
37   * A utility to store a reference to an <code>UIComponent</code>. Application developers
38   * should use this tool if they need to have a reference to an instance of the
39   * <code>UIComponent</code> class in <code>managed beans</code> that are longer than <b>requested scoped</b>
40   * --for example Session and Application Scoped.  The reference will return the UIComponent, if any, with
41   * the same scoped id as the Component used to create the reference, in the current UIViewRoot.
42   * 
43   * Use <code>newUIComponentReference()</code> to create a <code>ComponentReference</code> and
44   * use the <code>getComponent()</code> to look up the referenced <code>UIComponent</code>.
45   *  
46   * For example, a current weather application might have have a session scoped weatehrBean
47   * containing the current list of locations to report the weather on and support using a
48   * selectMany component to remove the locations:
49   * 
50   * <pre>
51   * <tr:selectManyCheckbox label="Locations" id="smc1" valuePassThru="true"
52   *                        binding="#{weatherBean.locationsSelectManyComponent}"
53   *                       value="#{weatherBean.locationsToRemove}">
54   *    <f:selectItems value="#{weatherBean.locationSelectItems}" id="si1"/>
55   * </tr:selectManyCheckbox>
56   * <tr:commandButton id="deleteCB" text="Remove Locations"
57   *                  actionListener="#{weatherBean.removeLocationListener}">
58   * </tr:commandButton>
59   * </pre>
60   * The weatherBean might looks like this:
61   * <pre>
62   * public class WeatherBean implements Serializable
63   * {
64   *   public void setLocationsToRemove(UIXSelectMany locationsToRemove)
65   *   {
66   *     _locationsToRemove = locationsToRemove;
67   *   }
68   *   
69   *   public UIXSelectMany getLocationsToRemove()
70   *   {
71   *     return _locationsToRemove;
72   *   }
73   *   
74   *   public void removeLocationListener(ActionEvent actionEvent)
75   *   {
76   *     ... code calling getLocationsToRemove() to get the UIXSelectMany ...
77   *   }
78   *
79   *   private UIXSelectMany _locationsToRemove
80   * }
81   * </pre>
82   * This code has several problems:
83   * <ol>
84   *   <li>Since UIComponents aren't Serializable, the class will fail serialization during fail-over
85   *   when default Serialization attempts to serialize _locationsToRemove.</li>
86   *   <li>If the user opens two windows on this page, only the last window rendered have the
87   *   correct UIXSelectMany instance.  If the remove locations button is pressed on the first
88   *   window after rendering the second window, the wrong UIXSelectMany instance will be used.</li>
89   *   <li>Since UIComponents aren't thread-safe, the above case could also result in bizare
90   *   behavior if requests from both windows were being processed by the application server at the
91   *   same time.</li>
92   *   <li>If the Trinidad view state token cache isn't used, or if the user navigates to this page
93   *   using the backbutton, a new UIXSelectMany instance will be created, which also won't match
94   *   the instance we are holding onto.</li>
95   *   <li>If we don't clear the UIXSelectMany instance when we navigate off of this page, we will
96   *   continue to pin the page's UIComponent tree in memory for the lifetime of the Session.
97   *   </li>
98   * </ol>
99   * Rewritten using ComponentReference, the weatherBean might looks like this:
100  * <pre>
101  * public class WeatherBean implements Serializable
102  * {
103  *   public void setLocationsToRemove(UIXSelectMany locationsToRemove)
104  *   {
105  *     _locationsToRemoveRef = UIComponentReference.newUIComponentReference(locationsToRemove);
106  *   }
107  *   
108  *   public UIXSelectMany getLocationsToRemove()
109  *   {
110  *     return _locationsToRemoveRef.getComponent();
111  *   }
112  *   
113  *   public void removeLocationListener(ActionEvent actionEvent)
114  *   {
115  *     ... code calling getLocationsToRemove() to get the UIXSelectMany ...
116  *   }
117  *
118  *   private UIComponentReference<UIXSelectMany> _locationsToRemoveRef
119  * }
120  * </pre>
121  * The above code saves a reference to the component passed to the managed bean and then
122  * retrieves the correct instance given the current UIViewRoot for this request whenever
123  * <code>getLocationsToRemove()</code> is called.
124  * <p><b>Please note:</b>
125  * <ul>
126  * <li>This class is <b>not completely</b> thread-safe, since it depends on <code>UIComponent</code>
127  * APIs, however the class is safe to use as long as either of the following is true
128  * <ol>
129  * <li>The component passed to <code>newUIComponentReference</code> has an id and is in the
130  * component hierarchy when newUIComponentReference is called and all subsequent calls to
131  * <code>getComponent</code> are made from Threads with a valid FacesContext
132  * </li>
133  * <li>The first call to <code>getComponent</code> is on the same Thread that
134  * <code>newUIComponentReference</code> was called on</li>
135  * </ol>
136  * </li>
137  * <li>The passed in <code>UIComponent</code> is <b>required</b> to have an <code>ID</code></li>
138  * <li>The reference will break if the <code>UIComponent</code> is moved between
139  * <code>NamingContainer</code>s <b>or</b>
140  * if any of the ancestor <code>NamingContainer</code>s have their IDs changed.</li>
141  * <li>The reference is persistable. <b>However</b> <code>UIComponent</code>s are not
142  * <code>Serializable</code> and therefore can not be used at any scope longer than request.</li>
143  * </ul>
144  * 
145  * @see ComponentReference#newUIComponentReference(UIComponent)
146  * @see ComponentReference#getComponent()
147  */
148 public abstract class ComponentReference<T extends UIComponent> implements Serializable
149 {
150   // don't allow other subclasses
151   private ComponentReference(List<Object> componentPath)
152   {
153     _componentPath = componentPath;
154   }
155 
156   /**
157    * Factory method to create an instance of the <code>ComponentReference</code> class, which
158    * returns a Serializable and often thread-safe reference to a 
159    * <code>UIComponent</code>.
160    * 
161    * @param component the <code>UIComponent</code> to create a reference to.
162    * @return <code>ComponentReference</code> the reference to the component
163    * @throws NullPointerException if component is <code>null</code>
164    */
165   public static <T extends UIComponent> ComponentReference<T> newUIComponentReference(T component)
166   {
167     // store the id of the component as a transient field since we can grab it from the scoped id
168     // but want it available to validate the component we found from the path
169     String compId = component.getId();
170 
171     // if the component is in the hierarchy, the topmost component will be the UIViewRoot
172     if ((compId != null) && (getUIViewRoot(component) != null))
173     {
174       // component has an id and is in the hierarachy, so we can use a stable reference
175       String scopedId = calculateScopedId(component, compId);
176       
177       return new StableComponentReference(scopedId, compId, calculateComponentPath(component));
178     }
179     else
180     {
181       // Oh well, deferred reference it is
182       ComponentReference<T> reference = new DeferredComponentReference<T>(component);
183       
184       // Add to the list of Referernces that may need initialization
185       _addToEnsureInitializationList(reference);
186       
187       return reference;
188     }
189   }
190   
191   /**
192    * This method will use a calculated "component path" to walk down to the <code>UIComponent</code>
193    * that is referenced by this class. If the component can not be found, the <code>getComponent()</code>
194    * will return <code>null</code>. 
195    * 
196    * @return the referenced <code>UIComponent</code> or <code>null</code> if it can not be found.
197    * @throws IllegalStateException if the component used to create the
198    * ComponentReference is not in the component tree or does <b>not</b> have an <code>Id</code>
199    * @see ComponentReference#newUIComponentReference(UIComponent)
200    */
201    @SuppressWarnings("unchecked")
202    public final T getComponent()
203    {
204      // get the scopedId, calculating it if necessary
205      String scopedId = getScopedId();
206          
207      UIComponent foundComponent = null;
208 
209      // In order to find the component with its
210      // calculated path, we need to start at the ViewRoot;
211      UIViewRoot root = FacesContext.getCurrentInstance().getViewRoot();
212 
213      List<Object> componentPath = _componentPath;
214      
215      if (componentPath != null) 
216      {
217        // Walk down the component tree, to the component we are looking for.
218        // We start at the ViewRoot and use the previous calculated "component path"
219        foundComponent = _walkPathToComponent(root, componentPath);
220      }
221 
222      // Check if we really found it with the previously created "component path"
223      if (foundComponent == null || (!getComponentId().equals(foundComponent.getId())))
224      {
225        // OK, we were not lucky with the calculated "component path", let's
226        // see if we can find it by using the "scoped ID" and the regular 
227        // findComponent();
228        foundComponent = root.findComponent(scopedId);
229 
230        // was the regular findComponent() successful ?
231        if (foundComponent != null)
232        {        
233          // OK, now let's rebuild the path
234          _componentPath = calculateComponentPath(foundComponent);
235        }
236      }
237 
238      return (T)foundComponent;
239    }
240 
241    /**
242     * Called by the framework to ensure that deferred ComponentReferences are completely
243     * initialized before the UIComponent that the ComponentReference is associated with
244     * is not longer valid.
245     * @throws IllegalStateException if ComponentReference isn't already initialized and 
246     * the component used to create the
247     * ComponentReference is not in the component tree or does <b>not</b> have an <code>Id</code>
248     */
249    public abstract void ensureInitialization();
250     
251   /**
252    * ComponentRefs are required to test for equivalence by the equivalence of their scoped ids
253    * @param o
254    * @return
255    */
256   @Override
257   public final boolean equals(Object o)
258   {
259     if (o == this)
260     {
261       return true;
262     }
263     else if (o instanceof ComponentReference)
264     {
265       return getScopedId().equals(((ComponentReference)o).getScopedId());
266     }
267     else
268     {
269       return false;
270     }
271   }
272  
273    /**
274     * ComponentRefs must use the hash code of their scoped id as their hash code
275     * @return
276     */
277   @Override
278   public final int hashCode()
279   {
280     return getScopedId().hashCode();
281   }
282   
283   @Override
284   public final String toString()
285   {
286     return super.toString() + ":" + getScopedId();
287   }
288   
289   /**
290    * Returns the scoped id for this ComponentReference
291    * @return
292    */
293   protected abstract String getScopedId();
294 
295   /**
296    * Returns the id of the Component that this ComponentReference points to
297    * @return
298    */
299   protected abstract String getComponentId();
300 
301   protected final void setComponentPath(List<Object> componentPath)
302   {
303     _componentPath = componentPath;
304   }
305 
306   /**
307    * Creates the "component path" started by the given <code>UIComponent</code> up the <code>UIViewRoot</code>
308    * of the underlying component tree. The hierarchy is stored in a <code>List</code> of <code>Object</code>s
309    * (the <code>componentHierarchyList</code> parameter). If the given <code>UIComponent</code> is nested in a 
310    * <code>Facet</code> of a <code>UIComponent</code>, we store the name of the actual <code>facet</code> in
311    * the list. If it is a regular child, we store its position/index. 
312    * 
313    * <p> 
314    * To calculate the <code>scopedID</code> we add the ID of every <code>NamingContainer</code> that we hit,
315    * while walking up the component tree, to the <code>scopedIdList</code>. 
316    * 
317    * @param component The <code>UIComponent</code> for this current iteration
318    * 
319    * @see #newUIComponentReference
320    */
321   protected static List<Object> calculateComponentPath(UIComponent component)
322   {
323     // setUp of list that stores information about the FACET name or the COMPONENT index
324     List<Object> componentHierarchyList = new ArrayList<Object>();
325 
326     // stash the component and parent , for the loop
327     UIComponent currComponent = component;
328     UIComponent currParent = currComponent.getParent();
329 
330     // enter the loop, if there is a parent for the current component
331     while(currParent != null)
332     {
333       int childIndex = currParent.getChildren().indexOf(currComponent);
334       
335       // is the given component a child of the parent?
336       if (childIndex != -1)
337       {          
338         // if so, add the INDEX (type: int) at the beginning of the list
339         componentHierarchyList.add(childIndex);
340       }
341       else
342       {
343         // If the component is not a child, it must be a facet.
344         // When the component is nested in a facet, we need to find
345         // the name of the embedding FACET
346         Set<Map.Entry<String, UIComponent>> entries = currParent.getFacets().entrySet();
347         for(Map.Entry<String, UIComponent> entry : entries)
348         {
349           if (currComponent.equals(entry.getValue()))
350           {
351             // once we identified the actual component/facet,
352             // we store the name (type: String)at the
353             // beginning of the list and quite the loop afterwards
354             componentHierarchyList.add(entry.getKey());
355             break;
356           }
357         }
358       }
359 
360       // set references for the next round of the loop
361       currComponent = currParent;
362       currParent = currParent.getParent();
363     }
364 
365     // done with the loop as >currComponent< has no own parent. Which
366     // means we must talk to <code>UIViewRoot</code> here.
367     // Otherwise the component is not connected to the tree, but we should have already checked this
368     // before calling this function
369     if (!(currComponent instanceof UIViewRoot))
370       throw new IllegalStateException(
371                    "The component " + component + " is NOT connected to the component tree");
372   
373     return componentHierarchyList;
374   }
375   
376   protected static String calculateScopedId(
377     UIComponent component,
378     String      componentId)
379   {
380     if (componentId == null)
381       throw new IllegalStateException("Can't create a ComponentReference for component " +
382                                       component +
383                                       " no id");    
384     int scopedIdLength = componentId.length();
385   
386     List<String> scopedIdList = new ArrayList<String>();
387    
388     // determine how many characters we need to store the scopedId.  We skip the component itself,
389     // because we have already accounted for its id
390     UIComponent currAncestor = component.getParent();
391     
392     while (currAncestor != null)
393     {
394       // add the sizes of all of the NamingContainer ancestors, plus 1 for each NamingContainer separator
395       if (currAncestor instanceof NamingContainer)
396       {
397         String currId = currAncestor.getId();
398         scopedIdLength += currId.length() + 1;
399       
400         // add the NamingContainer to the list of NamingContainers
401         scopedIdList.add(currId);
402       }
403       
404       currAncestor = currAncestor.getParent();
405     }
406     
407     // now append all of the NamingContaintes
408     return _createScopedId(scopedIdLength, scopedIdList, componentId);
409   }
410 
411   protected Object writeReplace() throws ObjectStreamException
412   {
413     // Only use the proxy when Serializing
414     return new SerializationProxy(getScopedId());
415   }
416   
417   private void readObject(@SuppressWarnings("unused") ObjectInputStream stream) throws InvalidObjectException
418   {
419     // We can't be deserialized directly
420     throw new InvalidObjectException("Proxy required");
421   } 
422 
423   /**
424    * Add a reference to the list of References that may need initialization later
425    * @param reference
426    */
427   private static void _addToEnsureInitializationList(ComponentReference<?> reference)
428   {
429     Map<String, Object> requestMap = 
430                             FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
431     
432     Collection<ComponentReference<?>> initializeList = (Collection<ComponentReference<?>>)
433                                              requestMap.get(_FINISH_INITIALIZATION_LIST_KEY);
434     
435     if (initializeList == null)
436     {
437       initializeList = new ArrayList<ComponentReference<?>>();
438       requestMap.put(_FINISH_INITIALIZATION_LIST_KEY, initializeList);
439     }
440     
441     initializeList.add(reference);
442   }
443 
444   /**
445    * Transform the <code>scopedIdList</code> of "important" component IDs to 
446    * generate the <code>scopedID</code> for the referenced <code>UIComponent</code>.
447    * 
448    * Uses the <code>scopedIdLength</code>
449    */
450   private static String _createScopedId(int scopedIdLength, List<String> scopedIdList, String componentId)
451   {
452     StringBuilder builder = new StringBuilder(scopedIdLength);
453 
454     for (int i = scopedIdList.size() - 1; i >= 0 ; i--)
455     {
456       builder.append(scopedIdList.get(i));
457       builder.append(NamingContainer.SEPARATOR_CHAR);
458     }
459 
460     builder.append(componentId);
461     
462     // store the (final) scopedId
463     return builder.toString();
464   }
465   
466   protected static UIViewRoot getUIViewRoot(UIComponent component)
467   {
468     // stash the component and parent , for the loop
469     UIComponent currComponent = component;
470     UIComponent currParent = currComponent.getParent();
471 
472     while(currParent != null)
473     {
474       currComponent = currParent;
475       currParent = currParent.getParent();
476     }
477     
478     return (currComponent instanceof UIViewRoot) ? (UIViewRoot)currComponent : null;
479   }
480 
481   /**
482    * Starts to walk down the component tree by the given <code>UIViewRoot</code>. It
483    * uses the <code>hierarchyInformationList</code> to check if the it needs to
484    * walk into a FACET or an INDEX of the component.
485    * 
486    * @see ComponentReference#calculateComponentPath(UIComponent)
487    */
488   private UIComponent _walkPathToComponent(UIViewRoot root, List<Object> componentPath)
489   {
490     UIComponent currFound = root;
491 
492     // iterate backwards since we appending the items starting from the component
493     for (int i = componentPath.size() - 1; i >= 0 ; i--)
494     {
495       Object location = componentPath.get(i);
496 
497       // integer means we need to get the kid at INDEX obj
498       // but let's not try to lookup from a component with
499       // no kids
500       if (location instanceof Integer)
501       {
502         int childIndex = ((Integer)location).intValue();
503         
504         List<UIComponent> children = currFound.getChildren();
505 
506         // make sure there is actually a child at this index
507         if (childIndex < children.size())
508         {
509           currFound = children.get(childIndex);
510         }
511         else
512         {
513           // something changed, there aren't enough children so give up
514           return null;
515         }
516       }
517       else
518       {
519         // there is only ONE child per facet! So get the
520         // component of FACET "obj"
521         String facetName = location.toString();
522 
523         currFound = currFound.getFacets().get(facetName);
524 
525         // component isn't under the same facet anymore, so give up
526         if (currFound == null)
527           return null;
528       }
529     }
530     return currFound;
531   }
532   
533   /**
534    * ComponentReference where the scopedId is calculatable at creation time
535    */
536   private static final class StableComponentReference extends ComponentReference
537   {
538     private StableComponentReference(String scopedId)
539     {      
540       this(scopedId,
541            // String.substring() is optimized to return this if the entire string
542            // is the substring, so no further optimization is necessary
543            scopedId.substring(scopedId.lastIndexOf(NamingContainer.SEPARATOR_CHAR)+1),
544            null);
545     }
546  
547     private StableComponentReference(
548       String scopedId,
549       String componentId,
550       List<Object> componentPath)
551     {
552       super(componentPath);
553 
554       if (scopedId == null)
555         throw new NullPointerException();
556       
557       _scopedId = scopedId;
558       _componentId = componentId;
559     }
560     
561     public void ensureInitialization()
562     {
563       // do nothing--stable references are always fully initialized
564     }
565 
566     protected String getScopedId()
567     {
568       return _scopedId;
569     }
570 
571     protected String getComponentId()
572     {
573       return _componentId;
574     }
575  
576     private final String _componentId;
577     private final String _scopedId;
578 
579     private static final long serialVersionUID = 1L;
580   }
581   
582   /**
583    * ComponentReference where the component isn't ready to have its ComponentReference calculated at
584    * creation time.  Instead we wait until getComponent() is called, or the ComponentReference is
585    * Serialized.
586    */
587   private static final class DeferredComponentReference<T extends UIComponent> extends ComponentReference
588   {
589     /**
590      * Private constructor, used by <code>ComponentReference.newUIComponentReference</code> 
591      * @param component the <code>UIComponent</code> we want to store the path for
592      */
593     private DeferredComponentReference(T component)
594     {
595       super(null);
596       
597       // temporarily store away the component
598       _component = component;
599     }
600 
601     public void ensureInitialization()
602     {
603       // getScopedId() ensures we are initialized
604       getScopedId();
605     }
606 
607     protected String getScopedId()
608     {
609       String scopedId = _scopedId;
610       
611       // we have no scopedId, so calculate the scopedId and finish initializing
612       // the DeferredComponentReference
613       if (scopedId == null)
614       {
615         UIComponent component = _component;
616         
617         // need to check that component isn't null because of possible race condition if this
618         // method is called from different threads.  In that case, scopedId will have been filled
619         // in, so we can return it
620         if (component != null)
621         {
622           String componentId = component.getId();
623                     
624           scopedId = calculateScopedId(component, componentId);
625           _scopedId = scopedId;
626           _componentId = componentId;
627           
628           // store away our component path while we can efficiently calculate it
629           setComponentPath(calculateComponentPath(component));
630           
631           _component = null;
632         }
633         else
634         {
635           scopedId = _scopedId;
636         }
637       }
638       
639       return scopedId;
640     }
641 
642     protected String getComponentId()
643     {
644       return _componentId;
645     }
646     
647     private transient T _component;
648     private transient volatile String _componentId;
649     private volatile String _scopedId;
650 
651     private static final long serialVersionUID = 1L;
652   }
653 
654   /**
655    * Proxy class for serializing ComponentReferences.  The Serialized for is simply the scopedId
656    */
657   private static final class SerializationProxy implements Serializable
658   {
659     SerializationProxy(String scopedId)
660     {
661       _scopedId = scopedId;
662     }
663 
664     private Object readResolve()
665     {
666       return new StableComponentReference(_scopedId);
667     }
668       
669     private final String _scopedId;
670 
671     private static final long serialVersionUID = 1L;
672   }
673 
674   private transient volatile List<Object> _componentPath;
675 
676   private static final String _FINISH_INITIALIZATION_LIST_KEY = ComponentReference.class.getName() +
677                                                                 "#FINISH_INITIALIZATION";
678   
679   private static final long serialVersionUID = -6803949693688638969L;
680 }