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 luck 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 }