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 }