1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. 5 * 6 * The contents of this file are subject to the terms of either the GNU 7 * General Public License Version 2 only ("GPL") or the Common Development 8 * and Distribution License("CDDL") (collectively, the "License"). You 9 * may not use this file except in compliance with the License. You can obtain 10 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html 11 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific 12 * language governing permissions and limitations under the License. 13 * 14 * When distributing the software, include this License Header Notice in each 15 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt. 16 * Sun designates this particular file as subject to the "Classpath" exception 17 * as provided by Sun in the GPL Version 2 section of the License file that 18 * accompanied this code. If applicable, add the following below the License 19 * Header, with the fields enclosed by brackets [] replaced by your own 20 * identifying information: "Portions Copyrighted [year] 21 * [name of copyright owner]" 22 * 23 * Contributor(s): 24 * 25 * If you wish your version of this file to be governed by only the CDDL or 26 * only the GPL Version 2, indicate your decision by adding "[Contributor] 27 * elects to include this software in this distribution under the [CDDL or GPL 28 * Version 2] license." If you don't indicate a single choice of license, a 29 * recipient has the option to distribute your version of this file under 30 * either the CDDL, the GPL Version 2 or to extend the choice of license to 31 * its licensees as provided above. However, if you add GPL Version 2 code 32 * and therefore, elected the GPL Version 2 license, then the option applies 33 * only if the new code is made subject to such option by the copyright 34 * holder. 35 */ 36 37 package javax.faces.component; 38 39 import javax.el.ValueExpression; 40 import javax.faces.FacesException; 41 import javax.faces.application.Application; 42 import javax.faces.application.FacesMessage; 43 import javax.faces.component.visit.VisitCallback; 44 import javax.faces.component.visit.VisitContext; 45 import javax.faces.component.visit.VisitHint; 46 import javax.faces.component.visit.VisitResult; 47 import javax.faces.context.FacesContext; 48 import javax.faces.el.ValueBinding; 49 import javax.faces.event.AbortProcessingException; 50 import javax.faces.event.FacesEvent; 51 import javax.faces.event.FacesListener; 52 import javax.faces.event.PhaseId; 53 import javax.faces.event.PostValidateEvent; 54 import javax.faces.event.PreValidateEvent; 55 import javax.faces.model.ArrayDataModel; 56 import javax.faces.model.DataModel; 57 import javax.faces.model.ListDataModel; 58 import javax.faces.model.ResultDataModel; 59 import javax.faces.model.ResultSetDataModel; 60 import javax.faces.model.ScalarDataModel; 61 import javax.servlet.jsp.jstl.sql.Result; 62 63 import java.io.IOException; 64 import java.io.Serializable; 65 import java.sql.ResultSet; 66 import java.util.Collection; 67 import java.util.Collections; 68 import java.util.List; 69 import java.util.Map; 70 import java.util.Iterator; 71 72 73 74 // ------------------------------------------------------------- Private Classes 75 // Private class to represent saved state information 76 /** 77 * <p><strong>UIData</strong> is a {@link UIComponent} that supports data 78 * binding to a collection of data objects represented by a {@link DataModel} 79 * instance, which is the current value of this component itself (typically 80 * established via a {@link ValueExpression}). During iterative processing over 81 * the rows of data in the data model, the object for the current row is exposed 82 * as a request attribute under the key specified by the <code>var</code> 83 * property.</p> 84 * <p/> 85 * <p>Only children of type {@link UIColumn} should be processed by renderers 86 * associated with this component.</p> 87 * <p/> 88 * <p>By default, the <code>rendererType</code> property is set to 89 * <code>javax.faces.Table</code>. This value can be changed by calling the 90 * <code>setRendererType()</code> method.</p> 91 */ 92 93 public class UIData extends UIComponentBase 94 implements NamingContainer, UniqueIdVendor { 95 96 // ------------------------------------------------------ Manifest Constants 97 98 99 /** 100 * <p>The standard component type for this component.</p> 101 */ 102 public static final String COMPONENT_TYPE = "javax.faces.Data"; 103 104 105 /** 106 * <p>The standard component family for this component.</p> 107 */ 108 public static final String COMPONENT_FAMILY = "javax.faces.Data"; 109 110 // ------------------------------------------------------------ Constructors 111 112 113 /** 114 * <p>Create a new {@link UIData} instance with default property 115 * values.</p> 116 */ 117 public UIData() { 118 119 super(); 120 setRendererType("javax.faces.Table"); 121 122 } 123 124 // ------------------------------------------------------ Instance Variables 125 126 127 /** 128 * Properties that are tracked by state saving. 129 */ 130 enum PropertyKeys { 131 /** 132 * <p>The first row number (zero-relative) to be displayed.</p> 133 */ 134 first, 135 136 /** 137 * <p>The zero-relative index of the current row number, or -1 for no 138 * current row association.</p> 139 */ 140 rowIndex, 141 142 /** 143 * <p>The number of rows to display, or zero for all remaining rows in the 144 * table.</p> 145 */ 146 rows, 147 148 /** 149 * <p>This map contains <code>SavedState</code> instances for each 150 * descendant component, keyed by the client identifier of the descendant. 151 * Because descendant client identifiers will contain the 152 * <code>rowIndex</code> value of the parent, per-row state information is 153 * actually preserved.</p> 154 */ 155 saved, 156 157 /** 158 * <p>The local value of this {@link UIComponent}.</p> 159 */ 160 value, 161 162 /** 163 * <p>The request scope attribute under which the data object for the 164 * current row will be exposed when iterating.</p> 165 */ 166 var, 167 168 /** 169 * <p>Last id vended by {@link UIData#createUniqueId(javax.faces.context.FacesContext, String)}.</p> 170 */ 171 lastId 172 } 173 174 175 /** 176 * <p>The {@link DataModel} associated with this component, lazily 177 * instantiated if requested. This object is not part of the saved and 178 * restored state of the component.</p> 179 */ 180 private DataModel model = null; 181 182 183 /** 184 * <p> During iteration through the rows of this table, This ivar is used to 185 * store the previous "var" value for this instance. When the row iteration 186 * is complete, this value is restored to the request map. 187 */ 188 private Object oldVar; 189 190 191 /** 192 * <p>Holds the base client ID that will be used to generate per-row 193 * client IDs (this will be null if this UIData is nested within another).</p> 194 * 195 * <p>This is not part of the component state.</p> 196 */ 197 private String baseClientId = null; 198 199 200 /** 201 * <p> Length of the cached <code>baseClientId</code> plus one for 202 * the {@link UINamingContainer#getSeparatorChar}. </p> 203 * 204 * <p>This is not part of the component state.</p> 205 */ 206 private int baseClientIdLength; 207 208 209 /** 210 * <p>StringBuilder used to build per-row client IDs.</p> 211 * 212 * <p>This is not part of the component state.</p> 213 */ 214 private StringBuilder clientIdBuilder = null; 215 216 217 /** 218 * <p>Flag indicating whether or not this UIData instance is nested 219 * within another UIData instance</p> 220 * 221 * <p>This is not part of the component state.</p> 222 */ 223 private Boolean isNested = null; 224 225 226 // -------------------------------------------------------------- Properties 227 228 229 public String getFamily() { 230 231 return (COMPONENT_FAMILY); 232 233 } 234 235 236 /** 237 * <p>Return the zero-relative row number of the first row to be 238 * displayed.</p> 239 */ 240 public int getFirst() { 241 242 return (Integer) getStateHelper().eval(PropertyKeys.first, 0); 243 244 } 245 246 247 /** 248 * <p>Set the zero-relative row number of the first row to be 249 * displayed.</p> 250 * 251 * @param first New first row number 252 * 253 * @throws IllegalArgumentException if <code>first</code> is negative 254 */ 255 public void setFirst(int first) { 256 257 if (first < 0) { 258 throw new IllegalArgumentException(String.valueOf(first)); 259 } 260 getStateHelper().put(PropertyKeys.first, first); 261 262 } 263 264 265 /** 266 * <p>Return the footer facet of this component (if any). A convenience 267 * method for <code>getFacet("footer")</code>.</p> 268 */ 269 public UIComponent getFooter() { 270 271 return getFacet("footer"); 272 273 } 274 275 276 /** 277 * <p>Set the footer facet of this component. A convenience method for 278 * <code>getFacets().put("footer", footer)</code>.</p> 279 * 280 * @param footer the new footer facet 281 * 282 * @throws NullPointerException if <code>footer</code> is <code>null</code> 283 */ 284 public void setFooter(UIComponent footer) { 285 286 getFacets().put("footer", footer); 287 288 } 289 290 291 /** 292 * <p>Return the header facet of this component (if any). A convenience 293 * method for <code>getFacet("header")</code>.</p> 294 */ 295 public UIComponent getHeader() { 296 297 return getFacet("header"); 298 299 } 300 301 302 /** 303 * <p>Set the header facet of this component. A convenience method for 304 * <code>getFacets().put("header", header)</code>.</p> 305 * 306 * @param header the new header facet 307 * 308 * @throws NullPointerException if <code>header</code> is <code>null</code> 309 */ 310 public void setHeader(UIComponent header) { 311 312 getFacets().put("header", header); 313 314 } 315 316 317 /** 318 * <p>Return a flag indicating whether there is <code>rowData</code> 319 * available at the current <code>rowIndex</code>. If no 320 * <code>wrappedData</code> is available, return <code>false</code>.</p> 321 * 322 * @throws FacesException if an error occurs getting the row availability 323 */ 324 public boolean isRowAvailable() { 325 326 return (getDataModel().isRowAvailable()); 327 328 } 329 330 331 /** 332 * <p>Return the number of rows in the underlying data model. If the number 333 * of available rows is unknown, return -1.</p> 334 * 335 * @throws FacesException if an error occurs getting the row count 336 */ 337 public int getRowCount() { 338 339 return (getDataModel().getRowCount()); 340 341 } 342 343 344 /** 345 * <p>Return the data object representing the data for the currently 346 * selected row index, if any.</p> 347 * 348 * @throws FacesException if an error occurs getting the row data 349 * @throws IllegalArgumentException if now row data is available at the 350 * currently specified row index 351 */ 352 public Object getRowData() { 353 354 return (getDataModel().getRowData()); 355 356 } 357 358 359 /** 360 * <p>Return the zero-relative index of the currently selected row. If we 361 * are not currently positioned on a row, return -1. This property is 362 * <strong>not</strong> enabled for value binding expressions.</p> 363 * 364 * @throws FacesException if an error occurs getting the row index 365 */ 366 public int getRowIndex() { 367 368 return (Integer) getStateHelper().eval(PropertyKeys.rowIndex, -1); 369 370 } 371 372 373 /** 374 * <p>Set the zero relative index of the current row, or -1 to indicate that 375 * no row is currently selected, by implementing the following algorithm. 376 * It is possible to set the row index at a value for which the underlying 377 * data collection does not contain any row data. Therefore, callers may 378 * use the <code>isRowAvailable()</code> method to detect whether row data 379 * will be available for use by the <code>getRowData()</code> method.</p> 380 *</p> 381 * <ul> 382 * <li>Save current state information for all descendant components (as 383 * described below). 384 * <li>Store the new row index, and pass it on to the {@link DataModel} 385 * associated with this {@link UIData} instance.</li> 386 * <li>If the new <code>rowIndex</code> value is -1: 387 * <ul> 388 * <li>If the <code>var</code> property is not null, 389 * remove the corresponding request scope attribute (if any).</li> 390 * <li>Reset the state information for all descendant components 391 * (as described below).</li> 392 * </ul></li> 393 * <li>If the new <code>rowIndex</code> value is not -1: 394 * <ul> 395 * <li>If the <code>var</code> property is not null, call 396 * <code>getRowData()</code> and expose the resulting data object 397 * as a request scope attribute whose key is the <code>var</code> 398 * property value.</li> 399 * <li>Reset the state information for all descendant components 400 * (as described below). 401 * </ul></li> 402 * </ul> 403 * 404 * <p>To save current state information for all descendant components, 405 * {@link UIData} must maintain per-row information for each descendant 406 * as follows:<p> 407 * <ul> 408 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, save 409 * the state of its <code>localValue</code> property.</li> 410 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 411 * save the state of the <code>localValueSet</code> property.</li> 412 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, save 413 * the state of the <code>valid</code> property.</li> 414 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 415 * save the state of the <code>submittedValue</code> property.</li> 416 * </ul> 417 * 418 * <p>To restore current state information for all descendant components, 419 * {@link UIData} must reference its previously stored information for the 420 * current <code>rowIndex</code> and call setters for each descendant 421 * as follows:</p> 422 * <ul> 423 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 424 * restore the <code>value</code> property.</li> 425 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 426 * restore the state of the <code>localValueSet</code> property.</li> 427 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 428 * restore the state of the <code>valid</code> property.</li> 429 * <li>If the descendant is an instance of <code>EditableValueHolder</code>, 430 * restore the state of the <code>submittedValue</code> property.</li> 431 * </ul> 432 * 433 * @param rowIndex The new row index value, or -1 for no associated row 434 * 435 * @throws FacesException if an error occurs setting the row index 436 * @throws IllegalArgumentException if <code>rowIndex</code> 437 * is less than -1 438 */ 439 public void setRowIndex(int rowIndex) { 440 441 // Save current state for the previous row index 442 saveDescendantState(); 443 444 // Update to the new row index 445 //this.rowIndex = rowIndex; 446 getStateHelper().put(PropertyKeys.rowIndex, rowIndex); 447 DataModel localModel = getDataModel(); 448 localModel.setRowIndex(rowIndex); 449 450 // if rowIndex is -1, clear the cache 451 if (rowIndex == -1) { 452 setDataModel(null); 453 } 454 455 // Clear or expose the current row data as a request scope attribute 456 String var = (String) getStateHelper().get(PropertyKeys.var); 457 if (var != null) { 458 Map<String, Object> requestMap = 459 getFacesContext().getExternalContext().getRequestMap(); 460 if (rowIndex == -1) { 461 oldVar = requestMap.remove(var); 462 } else if (isRowAvailable()) { 463 requestMap.put(var, getRowData()); 464 } else { 465 requestMap.remove(var); 466 if (null != oldVar) { 467 requestMap.put(var, oldVar); 468 oldVar = null; 469 } 470 } 471 } 472 473 // Reset current state information for the new row index 474 restoreDescendantState(); 475 476 } 477 478 479 /** 480 * <p>Return the number of rows to be displayed, or zero for all remaining 481 * rows in the table. The default value of this property is zero.</p> 482 */ 483 public int getRows() { 484 485 486 return (Integer) getStateHelper().eval(PropertyKeys.rows, 0); 487 488 } 489 490 491 /** 492 * <p>Set the number of rows to be displayed, or zero for all remaining rows 493 * in the table.</p> 494 * 495 * @param rows New number of rows 496 * 497 * @throws IllegalArgumentException if <code>rows</code> is negative 498 */ 499 public void setRows(int rows) { 500 501 if (rows < 0) { 502 throw new IllegalArgumentException(String.valueOf(rows)); 503 } 504 getStateHelper().put(PropertyKeys.rows, rows); 505 506 } 507 508 509 /** 510 * <p>Return the request-scope attribute under which the data object for the 511 * current row will be exposed when iterating. This property is 512 * <strong>not</strong> enabled for value binding expressions.</p> 513 */ 514 public String getVar() { 515 516 return (String) getStateHelper().get(PropertyKeys.var); 517 518 } 519 520 521 /** 522 * <p>Set the request-scope attribute under which the data object for the 523 * current row wil be exposed when iterating.</p> 524 * 525 * @param var The new request-scope attribute name 526 */ 527 public void setVar(String var) { 528 529 getStateHelper().put(PropertyKeys.var, var); 530 531 } 532 533 // ----------------------------------------------------- StateHolder Methods 534 535 536 537 538 /** 539 * <p>Return the value of the UIData. This value must either be 540 * be of type {@link DataModel}, or a type that can be adapted 541 * into a {@link DataModel}. <code>UIData</code> will automatically 542 * adapt the following types:</p> 543 * <ul> 544 * <li>Arrays</li> 545 * <li><code>java.util.List</code></li> 546 * <li><code>java.sql.ResultSet</code></li> 547 * <li><code>javax.servlet.jsp.jstl.sql.Result</code></li> 548 * </ul> 549 * <p>All other types will be adapted using the {@link ScalarDataModel} 550 * class, which will treat the object as a single row of data.</p> 551 */ 552 public Object getValue() { 553 554 return getStateHelper().eval(PropertyKeys.value); 555 556 } 557 558 559 /** 560 * <p>Set the value of the <code>UIData</code>. This value must either be 561 * be of type {@link DataModel}, or a type that can be adapted into a {@link 562 * DataModel}.</p> 563 * 564 * @param value the new value 565 */ 566 public void setValue(Object value) { 567 setDataModel(null); 568 getStateHelper().put(PropertyKeys.value, value); 569 570 } 571 572 // ----------------------------------------------------- UIComponent Methods 573 574 575 /** 576 * <p>If "name" is something other than "value", "var", or "rowIndex", rely 577 * on the superclass conversion from <code>ValueBinding</code> to 578 * <code>ValueExpression</code>.</p> 579 * 580 * @param name Name of the attribute or property for which to set a 581 * {@link ValueBinding} 582 * @param binding The {@link ValueBinding} to set, or <code>null</code> to 583 * remove any currently set {@link ValueBinding} 584 * 585 * @throws IllegalArgumentException if <code>name</code> is one of 586 * <code>id</code>, <code>parent</code>, 587 * <code>var</code>, or <code>rowIndex</code> 588 * @throws NullPointerException if <code>name</code> is <code>null</code> 589 * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, 590 *javax.el.ValueExpression)}. 591 */ 592 public void setValueBinding(String name, ValueBinding binding) { 593 594 if ("value".equals(name)) { 595 setDataModel(null); 596 } else if ("var".equals(name) || "rowIndex".equals(name)) { 597 throw new IllegalArgumentException(); 598 } 599 super.setValueBinding(name, binding); 600 601 } 602 603 /** 604 * <p>Set the {@link ValueExpression} used to calculate the value for the 605 * specified attribute or property name, if any. In addition, if a {@link 606 * ValueExpression} is set for the <code>value</code> property, remove any 607 * synthesized {@link DataModel} for the data previously bound to this 608 * component.</p> 609 * 610 * @param name Name of the attribute or property for which to set a 611 * {@link ValueExpression} 612 * @param binding The {@link ValueExpression} to set, or <code>null</code> 613 * to remove any currently set {@link ValueExpression} 614 * 615 * @throws IllegalArgumentException if <code>name</code> is one of 616 * <code>id</code>, <code>parent</code>, 617 * <code>var</code>, or <code>rowIndex</code> 618 * @throws NullPointerException if <code>name</code> is <code>null</code> 619 * @since 1.2 620 */ 621 public void setValueExpression(String name, ValueExpression binding) { 622 623 if ("value".equals(name)) { 624 this.model = null; 625 } else if ("var".equals(name) || "rowIndex".equals(name)) { 626 throw new IllegalArgumentException(); 627 } 628 super.setValueExpression(name, binding); 629 630 } 631 632 /** 633 * <p>Return a client identifier for this component that includes the 634 * current value of the <code>rowIndex</code> property, if it is not set to 635 * -1. This implies that multiple calls to <code>getClientId()</code> may 636 * return different results, but ensures that child components can 637 * themselves generate row-specific client identifiers (since {@link UIData} 638 * is a {@link NamingContainer}).</p> 639 * 640 * @throws NullPointerException if <code>context</code> is <code>null</code> 641 */ 642 public String getClientId(FacesContext context) { 643 644 if (context == null) { 645 throw new NullPointerException(); 646 } 647 648 // If baseClientId and clientIdBuilder are both null, this is the 649 // first time that getClientId() has been called. 650 // If we're not nested within another UIData, then: 651 // - create a new StringBuilder assigned to clientIdBuilder containing 652 // our client ID. 653 // - toString() the builder - this result will be our baseClientId 654 // for the duration of the component 655 // - append UINamingContainer.getSeparatorChar() to the builder 656 // If we are nested within another UIData, then: 657 // - create an empty StringBuilder that will be used to build 658 // this instance's ID 659 if (baseClientId == null && clientIdBuilder == null) { 660 if (!isNestedWithinUIData()) { 661 clientIdBuilder = new StringBuilder(super.getClientId(context)); 662 baseClientId = clientIdBuilder.toString(); 663 baseClientIdLength = (baseClientId.length() + 1); 664 clientIdBuilder.append(UINamingContainer.getSeparatorChar(context)); 665 clientIdBuilder.setLength(baseClientIdLength); 666 } else { 667 clientIdBuilder = new StringBuilder(); 668 } 669 } 670 int rowIndex = getRowIndex(); 671 if (rowIndex >= 0) { 672 String cid; 673 if (!isNestedWithinUIData()) { 674 // we're not nested, so the clientIdBuilder is already 675 // primed with clientID + 676 // UINamingContainer.getSeparatorChar(). Append the 677 // current rowIndex, and toString() the builder. reset 678 // the builder to it's primed state. 679 cid = clientIdBuilder.append(rowIndex).toString(); 680 clientIdBuilder.setLength(baseClientIdLength); 681 } else { 682 // we're nested, so we have to build the ID from scratch 683 // each time. Reuse the same clientIdBuilder instance 684 // for each call by resetting the length to 0 after 685 // the ID has been computed. 686 cid = clientIdBuilder.append(super.getClientId(context)) 687 .append(UINamingContainer.getSeparatorChar(context)).append(rowIndex) 688 .toString(); 689 clientIdBuilder.setLength(0); 690 } 691 return (cid); 692 } else { 693 if (!isNestedWithinUIData()) { 694 // Not nested and no row available, so just return our baseClientId 695 return (baseClientId); 696 } else { 697 // nested and no row available, return the result of getClientId(). 698 // this is necessary as the client ID will reflect the row that 699 // this table represents 700 return super.getClientId(context); 701 } 702 } 703 704 } 705 706 /** 707 * <p>Override behavior from {@link 708 * UIComponentBase#invokeOnComponent} to provide special care for 709 * positioning the data properly before finding the component and 710 * invoking the callback on it. If the argument 711 * <code>clientId</code> is equal to <code>this.getClientId()</code> 712 * simply invoke the <code>contextCallback</code>, passing the 713 * <code>context</code> argument and <b>this</b> as arguments, and 714 * return <code>true.</code> If the argument <code>clientId</code> 715 * is not equal to <code>this.getClientId()</code>, inspect each of 716 * the facet children of this <code>UIData</code> instance and for 717 * each one, compare its <code>clientId</code> with the argument 718 * <code>clientId</code>. If there is a match, invoke the 719 * <code>contextCallback</code>, passing the <code>context</code> 720 * argument and <b>this</b> as arguments, and return 721 * <code>true</code>. Otherwise, attempt to extract a rowIndex from 722 * the <code>clientId</code>. For example, if the argument 723 * <code>clientId</code> was <code>form:data:3:customerHeader</code> 724 * the rowIndex would be <code>3</code>. Let this value be called 725 * <code>newIndex</code>. The current rowIndex of this instance must 726 * be saved aside and restored before returning in all cases, 727 * regardless of the outcome of the search or if any exceptions are 728 * thrown in the process.</p> 729 * 730 * <p>The implementation of this method must never return <code>true</code> 731 * if setting the rowIndex of this instance to be equal to 732 * <code>newIndex</code> causes this instance to return <code>false</code> 733 * from {@link #isRowAvailable}.</p> 734 * 735 * @throws NullPointerException {@inheritDoc} 736 * @throws FacesException {@inheritDoc} Also throws <code>FacesException</code> 737 * if any exception is thrown when deriving the 738 * rowIndex from the argument <code>clientId</code>. 739 * @since 1.2 740 */ 741 public boolean invokeOnComponent(FacesContext context, String clientId, 742 ContextCallback callback) 743 throws FacesException { 744 if (null == context || null == clientId || null == callback) { 745 throw new NullPointerException(); 746 } 747 748 String myId = super.getClientId(context); 749 boolean found = false; 750 if (clientId.equals(myId)) { 751 try { 752 callback.invokeContextCallback(context, this); 753 return true; 754 } 755 catch (Exception e) { 756 throw new FacesException(e); 757 } 758 } 759 760 // check the facets, if any, of UIData 761 if (this.getFacetCount() > 0) { 762 for (UIComponent c : this.getFacets().values()) { 763 c.invokeOnComponent(context, clientId, callback); 764 } 765 } 766 767 // check column level facets, if any 768 if (this.getChildCount() > 0) { 769 for (UIComponent column : this.getChildren()) { 770 if (column instanceof UIColumn) { 771 if (column.getFacetCount() > 0) { 772 for (UIComponent facet : column.getFacets().values()) { 773 facet.invokeOnComponent(context, clientId, callback); 774 } 775 } 776 } 777 } 778 } 779 780 int lastSep, newRow, savedRowIndex = this.getRowIndex(); 781 try { 782 char sepChar = UINamingContainer.getSeparatorChar(context); 783 // If we need to strip out the rowIndex from our id 784 // PENDING(edburns): is this safe with respect to I18N? 785 if (myId.endsWith(sepChar + Integer.toString(savedRowIndex, 10))) { 786 lastSep = myId.lastIndexOf(sepChar); 787 assert (-1 != lastSep); 788 myId = myId.substring(0, lastSep); 789 } 790 791 // myId will be something like form:outerData for a non-nested table, 792 // and form:outerData:3:data for a nested table. 793 // clientId will be something like form:outerData:3:outerColumn 794 // for a non-nested table. clientId will be something like 795 // outerData:3:data:3:input for a nested table. 796 if (clientId.startsWith(myId)) { 797 int preRowIndexSep, postRowIndexSep; 798 799 if (-1 != (preRowIndexSep = 800 clientId.indexOf(sepChar, 801 myId.length()))) { 802 // Check the length 803 if (++preRowIndexSep < clientId.length()) { 804 if (-1 != (postRowIndexSep = 805 clientId.indexOf(sepChar, 806 preRowIndexSep + 1))) { 807 try { 808 newRow = Integer 809 .valueOf(clientId.substring(preRowIndexSep, 810 postRowIndexSep)) 811 .intValue(); 812 } catch (NumberFormatException ex) { 813 // PENDING(edburns): I18N 814 String message = 815 "Trying to extract rowIndex from clientId \'" 816 + 817 clientId 818 + "\' " 819 + ex.getMessage(); 820 throw new NumberFormatException(message); 821 } 822 this.setRowIndex(newRow); 823 if (this.isRowAvailable()) { 824 found = super.invokeOnComponent(context, 825 clientId, 826 callback); 827 } 828 } 829 } 830 } 831 } 832 } 833 catch (FacesException fe) { 834 throw fe; 835 } 836 catch (Exception e) { 837 throw new FacesException(e); 838 } 839 finally { 840 this.setRowIndex(savedRowIndex); 841 } 842 return found; 843 } 844 845 846 /** 847 * <p>Override the default {@link UIComponentBase#queueEvent} processing to 848 * wrap any queued events in a wrapper so that we can reset the current row 849 * index in <code>broadcast()</code>.</p> 850 * 851 * @param event {@link FacesEvent} to be queued 852 * 853 * @throws IllegalStateException if this component is not a descendant of a 854 * {@link UIViewRoot} 855 * @throws NullPointerException if <code>event</code> is <code>null</code> 856 */ 857 public void queueEvent(FacesEvent event) { 858 859 super.queueEvent(new WrapperEvent(this, event, getRowIndex())); 860 861 } 862 863 864 /** 865 * <p>Override the default {@link UIComponentBase#broadcast} processing to 866 * unwrap any wrapped {@link FacesEvent} and reset the current row index, 867 * before the event is actually broadcast. For events that we did not wrap 868 * (in <code>queueEvent()</code>), default processing will occur.</p> 869 * 870 * @param event The {@link FacesEvent} to be broadcast 871 * 872 * @throws AbortProcessingException Signal the JavaServer Faces 873 * implementation that no further 874 * processing on the current event should 875 * be performed 876 * @throws IllegalArgumentException if the implementation class of this 877 * {@link FacesEvent} is not supported by 878 * this component 879 * @throws NullPointerException if <code>event</code> is <code>null</code> 880 */ 881 public void broadcast(FacesEvent event) 882 throws AbortProcessingException { 883 884 if (!(event instanceof WrapperEvent)) { 885 super.broadcast(event); 886 return; 887 } 888 FacesContext context = FacesContext.getCurrentInstance(); 889 // Set up the correct context and fire our wrapped event 890 WrapperEvent revent = (WrapperEvent) event; 891 if (isNestedWithinUIData()) { 892 setDataModel(null); 893 } 894 int oldRowIndex = getRowIndex(); 895 setRowIndex(revent.getRowIndex()); 896 FacesEvent rowEvent = revent.getFacesEvent(); 897 UIComponent source = rowEvent.getComponent(); 898 UIComponent compositeParent = null; 899 try { 900 if (!UIComponent.isCompositeComponent(source)) { 901 compositeParent = UIComponent.getCompositeComponentParent(source); 902 } 903 if (compositeParent != null) { 904 compositeParent.pushComponentToEL(context, null); 905 } 906 source.pushComponentToEL(context, null); 907 source.broadcast(rowEvent); 908 } finally { 909 source.popComponentFromEL(context); 910 if (compositeParent != null) { 911 compositeParent.popComponentFromEL(context); 912 } 913 } 914 setRowIndex(oldRowIndex); 915 916 } 917 918 /** 919 * <p>In addition to the default behavior, ensure that any saved per-row 920 * state for our child input components is discarded unless it is needed to 921 * rerender the current page with errors. 922 * 923 * @param context FacesContext for the current request 924 * 925 * @throws IOException if an input/output error occurs while 926 * rendering 927 * @throws NullPointerException if <code>context</code> is <code>null</code> 928 */ 929 public void encodeBegin(FacesContext context) throws IOException { 930 931 preEncode(context); 932 super.encodeBegin(context); 933 934 } 935 936 937 /** 938 * <p>Override the default {@link UIComponentBase#processDecodes} processing 939 * to perform the following steps.</p> <ul> <li>If the <code>rendered</code> 940 * property of this {@link UIComponent} is <code>false</code>, skip further 941 * processing.</li> <li>Set the current <code>rowIndex</code> to -1.</li> 942 * <li>Call the <code>processDecodes()</code> method of all facets of this 943 * {@link UIData}, in the order determined by a call to 944 * <code>getFacets().keySet().iterator()</code>.</li> <li>Call the 945 * <code>processDecodes()</code> method of all facets of the {@link 946 * UIColumn} children of this {@link UIData}.</li> <li>Iterate over the set 947 * of rows that were included when this component was rendered (i.e. those 948 * defined by the <code>first</code> and <code>rows</code> properties), 949 * performing the following processing for each row: <ul> <li>Set the 950 * current <code>rowIndex</code> to the appropriate value for this row.</li> 951 * <li>If <code>isRowAvailable()</code> returns <code>true</code>, iterate 952 * over the children components of each {@link UIColumn} child of this 953 * {@link UIData} component, calling the <code>processDecodes()</code> 954 * method for each such child.</li> </ul></li> <li>Set the current 955 * <code>rowIndex</code> to -1.</li> <li>Call the <code>decode()</code> 956 * method of this component.</li> <li>If a <code>RuntimeException</code> is 957 * thrown during decode processing, call {@link FacesContext#renderResponse} 958 * and re-throw the exception.</li> </ul> 959 * 960 * @param context {@link FacesContext} for the current request 961 * 962 * @throws NullPointerException if <code>context</code> is <code>null</code> 963 */ 964 public void processDecodes(FacesContext context) { 965 966 if (context == null) { 967 throw new NullPointerException(); 968 } 969 if (!isRendered()) { 970 return; 971 } 972 973 preDecode(context); 974 iterate(context, PhaseId.APPLY_REQUEST_VALUES); 975 decode(context); 976 977 } 978 979 980 /** 981 * <p>Override the default {@link UIComponentBase#processValidators} 982 * processing to perform the following steps.</p> <ul> <li>If the 983 * <code>rendered</code> property of this {@link UIComponent} is 984 * <code>false</code>, skip further processing.</li> <li>Set the current 985 * <code>rowIndex</code> to -1.</li> <li>Call the <code>processValidators()</code> 986 * method of all facets of this {@link UIData}, in the order determined by a 987 * call to <code>getFacets().keySet().iterator()</code>.</li> <li>Call the 988 * <code>processValidators()</code> method of all facets of the {@link 989 * UIColumn} children of this {@link UIData}.</li> <li>Iterate over the set 990 * of rows that were included when this component was rendered (i.e. those 991 * defined by the <code>first</code> and <code>rows</code> properties), 992 * performing the following processing for each row: <ul> <li>Set the 993 * current <code>rowIndex</code> to the appropriate value for this row.</li> 994 * <li>If <code>isRowAvailable()</code> returns <code>true</code>, iterate 995 * over the children components of each {@link UIColumn} child of this 996 * {@link UIData} component, calling the <code>processValidators()</code> 997 * method for each such child.</li> </ul></li> <li>Set the current 998 * <code>rowIndex</code> to -1.</li> </ul> 999 * 1000 * @param context {@link FacesContext} for the current request 1001 * 1002 * @throws NullPointerException if <code>context</code> is <code>null</code> 1003 */ 1004 public void processValidators(FacesContext context) { 1005 1006 if (context == null) { 1007 throw new NullPointerException(); 1008 } 1009 if (!isRendered()) { 1010 return; 1011 } 1012 Application app = context.getApplication(); 1013 app.publishEvent(context, PreValidateEvent.class, this); 1014 preValidate(context); 1015 iterate(context, PhaseId.PROCESS_VALIDATIONS); 1016 app.publishEvent(context, PostValidateEvent.class, this); 1017 1018 } 1019 1020 1021 /** 1022 * <p>Override the default {@link UIComponentBase#processUpdates} 1023 * processing to perform the following steps.</p> 1024 * <ul> 1025 * <li>If the <code>rendered</code> property of this {@link UIComponent} 1026 * is <code>false</code>, skip further processing.</li> 1027 * <li>Set the current <code>rowIndex</code> to -1.</li> 1028 * <li>Call the <code>processUpdates()</code> method of all facets 1029 * of this {@link UIData}, in the order determined 1030 * by a call to <code>getFacets().keySet().iterator()</code>.</li> 1031 * <li>Call the <code>processUpdates()</code> method of all facets 1032 * of the {@link UIColumn} children of this {@link UIData}.</li> 1033 * <li>Iterate over the set of rows that were included when this 1034 * component was rendered (i.e. those defined by the <code>first</code> 1035 * and <code>rows</code> properties), performing the following 1036 * processing for each row: 1037 * <ul> 1038 * <li>Set the current <code>rowIndex</code> to the appropriate 1039 * value for this row.</li> 1040 * <li>If <code>isRowAvailable()</code> returns <code>true</code>, 1041 * iterate over the children components of each {@link UIColumn} 1042 * child of this {@link UIData} component, calling the 1043 * <code>processUpdates()</code> method for each such child.</li> 1044 * </ul></li> 1045 * <li>Set the current <code>rowIndex</code> to -1.</li> 1046 * </ul> 1047 * 1048 * @param context {@link FacesContext} for the current request 1049 * 1050 * @throws NullPointerException if <code>context</code> is <code>null</code> 1051 */ 1052 public void processUpdates(FacesContext context) { 1053 1054 if (context == null) { 1055 throw new NullPointerException(); 1056 } 1057 if (!isRendered()) { 1058 return; 1059 } 1060 1061 preUpdate(context); 1062 iterate(context, PhaseId.UPDATE_MODEL_VALUES); 1063 // This is not a EditableValueHolder, so no further processing is required 1064 1065 } 1066 1067 public String createUniqueId(FacesContext context, String seed) { 1068 Integer i = (Integer) getStateHelper().get(PropertyKeys.lastId); 1069 int lastId = ((i != null) ? i : 0); 1070 getStateHelper().put(PropertyKeys.lastId, ++lastId); 1071 return UIViewRoot.UNIQUE_ID_PREFIX + (seed == null ? lastId : seed); 1072 } 1073 1074 /** 1075 * <p class="changed_added_2_0">Override the behavior in {@link 1076 * UIComponent#visitTree} to handle iteration correctly.</p> 1077 * 1078 * <div class="changed_added_2_0"> 1079 1080 * <p>If the {@link UIComponent#isVisitable} method of this instance 1081 * returns <code>false</code>, take no action and return.</p> 1082 1083 * <p>Otherwise, save aside the result of a call to {@link 1084 * #getRowIndex}. Call {@link UIComponent#pushComponentToEL} and 1085 * invoke the visit callback on this <code>UIData</code> instance as 1086 * described in {@link UIComponent#visitTree}. Let the result of 1087 * the invoctaion be <em>visitResult</em>. If <em>visitResult</em> 1088 * is {@link VisitResult#COMPLETE}, take no further action and 1089 * return <code>true</code>. Otherwise, determine if we need to 1090 * visit our children. The default implementation calls {@link 1091 * VisitContext#getSubtreeIdsToVisit} passing <code>this</code> as 1092 * the argument. If the result of that call is non-empty, let 1093 * <em>doVisitChildren</em> be <code>true</code>. If 1094 * <em>doVisitChildren</em> is <code>true</code> and 1095 * <em>visitResult</em> is {@link VisitResult#ACCEPT}, take the 1096 * following action.<p> 1097 1098 * <ul> 1099 1100 * <li><p>If this component has facets, call {@link 1101 * UIComponent#getFacets} on this instance and invoke the 1102 * <code>values()</code> method. For each 1103 * <code>UIComponent</code> in the returned <code>Map</code>, 1104 * call {@link UIComponent#visitTree}.</p></li> 1105 1106 * <li><p>If this component has children, for each child 1107 * <code>UIComponent</code> retured from calling {@link 1108 * #getChildren} on this instance, if the child has facets, call 1109 * {@link UIComponent#getFacets} on the child instance and invoke 1110 * the <code>values()</code> method. For each 1111 * <code>UIComponent</code> in the returned <code>Map</code>, 1112 * call {@link UIComponent#visitTree}.</p></li> 1113 1114 * <li><p>Iterate over the columns and rows.</p> 1115 1116 * <ul> 1117 1118 * <li><p>Let <em>rowsToProcess</em> be the return from {@link 1119 * #getRows}. </p></li> 1120 1121 * <li><p>Let <em>rowIndex</em> be the return from {@link 1122 * #getFirst} - 1.</p></li> 1123 1124 * <li><p>While the number of rows processed is less than 1125 * <em>rowsToProcess</em>, take the following actions.</p> 1126 1127 * <p>Call {@link #setRowIndex}, passing the current row index.</p> 1128 1129 * <p>If {@link #isRowAvailable} returns <code>false</code>, take no 1130 * further action and return <code>false</code>.</p> 1131 1132 * <p>For each child component of this <code>UIData</code> that is 1133 * also an instance of {@link UIColumn}, call {@link 1134 * UIComponent#visitTree} on the child. If such a call returns 1135 * <code>true</code>, terminate iteration and return 1136 * <code>true</code>. Take no action on non <code>UIColumn</code> 1137 * children.</p> 1138 1139 * </li> 1140 1141 * </ul> 1142 1143 * </li> 1144 1145 * </ul> 1146 1147 * <p>Call {@link #popComponentFromEL} and restore the saved row 1148 * index with a call to {@link #setRowIndex}.</p> 1149 1150 * <p>Return <code>false</code> to allow the visiting to 1151 * continue.</p> 1152 1153 * </div> 1154 * 1155 * @param context the <code>VisitContext</code> that provides 1156 * context for performing the visit. 1157 * 1158 * @param callback the callback to be invoked for each node 1159 * encountered in the visit. 1160 1161 * @throws NullPointerException if any of the parameters are 1162 * <code>null</code>. 1163 1164 * 1165 */ 1166 @Override 1167 public boolean visitTree(VisitContext context, 1168 VisitCallback callback) { 1169 1170 // First check to see whether we are visitable. If not 1171 // short-circuit out of this subtree, though allow the 1172 // visit to proceed through to other subtrees. 1173 if (!isVisitable(context)) 1174 return false; 1175 1176 // Clear out the row index is one is set so that 1177 // we start from a clean slate. 1178 int oldRowIndex = getRowIndex(); 1179 setRowIndex(-1); 1180 1181 // Push ourselves to EL 1182 FacesContext facesContext = context.getFacesContext(); 1183 pushComponentToEL(facesContext, null); 1184 1185 try { 1186 1187 // Visit ourselves. Note that we delegate to the 1188 // VisitContext to actually perform the visit. 1189 VisitResult result = context.invokeVisitCallback(this, callback); 1190 1191 // If the visit is complete, short-circuit out and end the visit 1192 if (result == VisitResult.COMPLETE) 1193 return true; 1194 1195 // Visit children, short-circuiting as necessary 1196 if ((result == VisitResult.ACCEPT) && doVisitChildren(context)) { 1197 1198 // First visit facets 1199 if (visitFacets(context, callback)) 1200 return true; 1201 1202 // Next column facets 1203 if (visitColumnFacets(context, callback)) 1204 return true; 1205 1206 // And finally, visit rows 1207 if (visitColumnsAndRows(context, callback)) 1208 return true; 1209 } 1210 } 1211 finally { 1212 // Clean up - pop EL and restore old row index 1213 popComponentFromEL(facesContext); 1214 setRowIndex(oldRowIndex); 1215 } 1216 1217 // Return false to allow the visit to continue 1218 return false; 1219 } 1220 1221 1222 // --------------------------------------------------------- Protected Methods 1223 1224 1225 /** 1226 * <p>Return the internal {@link DataModel} object representing the data 1227 * objects that we will iterate over in this component's rendering.</p> 1228 * <p/> 1229 * <p>If the model has been cached by a previous call to {@link 1230 * #setDataModel}, return it. Otherwise call {@link #getValue}. If the 1231 * result is null, create an empty {@link ListDataModel} and return it. If 1232 * the result is an instance of {@link DataModel}, return it. Otherwise, 1233 * adapt the result as described in {@link #getValue} and return it.</p> 1234 */ 1235 protected DataModel getDataModel() { 1236 1237 // Return any previously cached DataModel instance 1238 if (this.model != null) { 1239 return (model); 1240 } 1241 1242 // Synthesize a DataModel around our current value if possible 1243 Object current = getValue(); 1244 if (current == null) { 1245 setDataModel(new ListDataModel(Collections.EMPTY_LIST)); 1246 } else if (current instanceof DataModel) { 1247 setDataModel((DataModel) current); 1248 } else if (current instanceof List) { 1249 setDataModel(new ListDataModel((List) current)); 1250 } else if (Object[].class.isAssignableFrom(current.getClass())) { 1251 setDataModel(new ArrayDataModel((Object[]) current)); 1252 } else if (current instanceof ResultSet) { 1253 setDataModel(new ResultSetDataModel((ResultSet) current)); 1254 } else if (current instanceof Result) { 1255 setDataModel(new ResultDataModel((Result) current)); 1256 } else { 1257 setDataModel(new ScalarDataModel(current)); 1258 } 1259 return (model); 1260 1261 } 1262 1263 /** 1264 * <p>Set the internal DataModel. This <code>UIData</code> instance must 1265 * use the given {@link DataModel} as its internal value representation from 1266 * now until the next call to <code>setDataModel</code>. If the given 1267 * <code>DataModel</code> is <code>null</code>, the internal 1268 * <code>DataModel</code> must be reset in a manner so that the next call to 1269 * {@link #getDataModel} causes lazy instantion of a newly refreshed 1270 * <code>DataModel</code>.</p> 1271 * <p/> 1272 * <p>Subclasses might call this method if they either want to restore the 1273 * internal <code>DataModel</code> during the <em>Restore View</em> phase or 1274 * if they want to explicitly refresh the current <code>DataModel</code> for 1275 * the <em>Render Response</em> phase.</p> 1276 * 1277 * @param dataModel the new <code>DataModel</code> or <code>null</code> to 1278 * cause the model to be refreshed. 1279 */ 1280 1281 protected void setDataModel(DataModel dataModel) { 1282 this.model = dataModel; 1283 } 1284 1285 // ---------------------------------------------------- Private Methods 1286 1287 1288 // Perform pre-decode initialization work. Note that this 1289 // initialization may be performed either during a normal decode 1290 // (ie. processDecodes()) or during a tree visit (ie. visitTree()). 1291 private void preDecode(FacesContext context) { 1292 setDataModel(null); // Re-evaluate even with server-side state saving 1293 Map<String, SavedState> saved = 1294 (Map<String, SavedState>) getStateHelper().get(PropertyKeys.saved); 1295 if (null == saved || !keepSaved(context)) { 1296 //noinspection CollectionWithoutInitialCapacity 1297 getStateHelper().remove(PropertyKeys.saved); 1298 } 1299 } 1300 1301 // Perform pre-validation initialization work. Note that this 1302 // initialization may be performed either during a normal validation 1303 // (ie. processValidators()) or during a tree visit (ie. visitTree()). 1304 private void preValidate(FacesContext context) { 1305 if (isNestedWithinUIData()) { 1306 setDataModel(null); 1307 } 1308 } 1309 1310 // Perform pre-update initialization work. Note that this 1311 // initialization may be performed either during normal update 1312 // (ie. processUpdates()) or during a tree visit (ie. visitTree()). 1313 private void preUpdate(FacesContext context) { 1314 if (isNestedWithinUIData()) { 1315 setDataModel(null); 1316 } 1317 } 1318 1319 // Perform pre-encode initialization work. Note that this 1320 // initialization may be performed either during a normal encode 1321 // (ie. encodeBegin()) or during a tree visit (ie. visitTree()). 1322 private void preEncode(FacesContext context) { 1323 setDataModel(null); // re-evaluate even with server-side state saving 1324 if (!keepSaved(context)) { 1325 ////noinspection CollectionWithoutInitialCapacity 1326 //saved = new HashMap<String, SavedState>(); 1327 getStateHelper().remove(PropertyKeys.saved); 1328 } 1329 } 1330 1331 /** 1332 * <p>Perform the appropriate phase-specific processing and per-row 1333 * iteration for the specified phase, as follows: 1334 * <ul> 1335 * <li>Set the <code>rowIndex</code> property to -1, and process the facets 1336 * of this {@link UIData} component exactly once.</li> 1337 * <li>Set the <code>rowIndex</code> property to -1, and process the facets 1338 * of the {@link UIColumn} children of this {@link UIData} component 1339 * exactly once.</li> 1340 * <li>Iterate over the relevant rows, based on the <code>first</code> 1341 * and <code>row</code> properties, and process the children 1342 * of the {@link UIColumn} children of this {@link UIData} component 1343 * once per row.</li> 1344 * </ul> 1345 * 1346 * @param context {@link FacesContext} for the current request 1347 * @param phaseId {@link PhaseId} of the phase we are currently running 1348 */ 1349 private void iterate(FacesContext context, PhaseId phaseId) { 1350 1351 // Process each facet of this component exactly once 1352 setRowIndex(-1); 1353 if (getFacetCount() > 0) { 1354 for (UIComponent facet : getFacets().values()) { 1355 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { 1356 facet.processDecodes(context); 1357 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { 1358 facet.processValidators(context); 1359 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { 1360 facet.processUpdates(context); 1361 } else { 1362 throw new IllegalArgumentException(); 1363 } 1364 } 1365 } 1366 1367 // Process each facet of our child UIColumn components exactly once 1368 setRowIndex(-1); 1369 if (getChildCount() > 0) { 1370 for (UIComponent column : getChildren()) { 1371 if (!(column instanceof UIColumn) || !column.isRendered()) { 1372 continue; 1373 } 1374 if (column.getFacetCount() > 0) { 1375 for (UIComponent columnFacet : column.getFacets().values()) { 1376 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { 1377 columnFacet.processDecodes(context); 1378 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { 1379 columnFacet.processValidators(context); 1380 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { 1381 columnFacet.processUpdates(context); 1382 } else { 1383 throw new IllegalArgumentException(); 1384 } 1385 } 1386 } 1387 } 1388 } 1389 1390 // Iterate over our UIColumn children, once per row 1391 int processed = 0; 1392 int rowIndex = getFirst() - 1; 1393 int rows = getRows(); 1394 1395 while (true) { 1396 1397 // Have we processed the requested number of rows? 1398 if ((rows > 0) && (++processed > rows)) { 1399 break; 1400 } 1401 1402 // Expose the current row in the specified request attribute 1403 setRowIndex(++rowIndex); 1404 if (!isRowAvailable()) { 1405 break; // Scrolled past the last row 1406 } 1407 1408 // Perform phase-specific processing as required 1409 // on the *children* of the UIColumn (facets have 1410 // been done a single time with rowIndex=-1 already) 1411 if (getChildCount() > 0) { 1412 for (UIComponent kid : getChildren()) { 1413 if (!(kid instanceof UIColumn) || !kid.isRendered()) { 1414 continue; 1415 } 1416 if (kid.getChildCount() > 0) { 1417 for (UIComponent grandkid : kid.getChildren()) { 1418 if (!grandkid.isRendered()) { 1419 continue; 1420 } 1421 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { 1422 grandkid.processDecodes(context); 1423 } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { 1424 grandkid.processValidators(context); 1425 } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { 1426 grandkid.processUpdates(context); 1427 } else { 1428 throw new IllegalArgumentException(); 1429 } 1430 } 1431 } 1432 } 1433 } 1434 1435 } 1436 1437 // Clean up after ourselves 1438 setRowIndex(-1); 1439 1440 } 1441 1442 // Tests whether we need to visit our children as part of 1443 // a tree visit 1444 private boolean doVisitChildren(VisitContext context) { 1445 1446 // Just need to check whether there are any ids under this 1447 // subtree. Make sure row index is cleared out since 1448 // getSubtreeIdsToVisit() needs our row-less client id. 1449 setRowIndex(-1); 1450 Collection<String> idsToVisit = context.getSubtreeIdsToVisit(this); 1451 assert(idsToVisit != null); 1452 1453 // All ids or non-empty collection means we need to visit our children. 1454 return (!idsToVisit.isEmpty()); 1455 } 1456 1457 // Performs pre-phase initialization before visiting children 1458 // (if necessary). 1459 private void preVisitChildren(VisitContext visitContext) { 1460 1461 // If EXECUTE_LIFECYCLE hint is set, we need to do 1462 // lifecycle-related initialization before visiting children 1463 if (visitContext.getHints().contains(VisitHint.EXECUTE_LIFECYCLE)) { 1464 FacesContext facesContext = visitContext.getFacesContext(); 1465 PhaseId phaseId = facesContext.getCurrentPhaseId(); 1466 1467 if (phaseId == PhaseId.APPLY_REQUEST_VALUES) 1468 preDecode(facesContext); 1469 else if (phaseId == PhaseId.PROCESS_VALIDATIONS) 1470 preValidate(facesContext); 1471 else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) 1472 preUpdate(facesContext); 1473 else if (phaseId == PhaseId.RENDER_RESPONSE) 1474 preEncode(facesContext); 1475 } 1476 } 1477 1478 // Visit each facet of this component exactly once 1479 private boolean visitFacets(VisitContext context, VisitCallback callback) { 1480 1481 setRowIndex(-1); 1482 if (getFacetCount() > 0) { 1483 for (UIComponent facet : getFacets().values()) { 1484 if (facet.visitTree(context, callback)) 1485 return true; 1486 } 1487 } 1488 1489 return false; 1490 } 1491 1492 // Visit each facet of our child UIColumn components exactly once 1493 private boolean visitColumnFacets(VisitContext context, 1494 VisitCallback callback) { 1495 setRowIndex(-1); 1496 if (getChildCount() > 0) { 1497 for (UIComponent column : getChildren()) { 1498 if (column.getFacetCount() > 0) { 1499 for (UIComponent columnFacet : column.getFacets().values()) { 1500 if (columnFacet.visitTree(context, callback)) 1501 return true; 1502 } 1503 } 1504 } 1505 } 1506 1507 return false; 1508 } 1509 1510 // Visit each column and row 1511 private boolean visitColumnsAndRows(VisitContext context, VisitCallback callback) { 1512 1513 1514 // first, visit all columns 1515 if (getChildCount() > 0) { 1516 for (UIComponent kid : getChildren()) { 1517 if (!(kid instanceof UIColumn)) { 1518 continue; 1519 } 1520 if (kid.visitTree(context, callback)) { 1521 return true; 1522 } 1523 } 1524 } 1525 1526 // Iterate over our UIColumn children, once per row 1527 int processed = 0; 1528 int rowIndex = getFirst() - 1; 1529 int rows = getRows(); 1530 1531 while (true) { 1532 1533 // Have we processed the requested number of rows? 1534 if ((rows > 0) && (++processed > rows)) { 1535 break; 1536 } 1537 1538 // Expose the current row in the specified request attribute 1539 setRowIndex(++rowIndex); 1540 if (!isRowAvailable()) { 1541 break; // Scrolled past the last row 1542 } 1543 1544 // Visit as required on the *children* of the UIColumn 1545 // (facets have been done a single time with rowIndex=-1 already) 1546 if (getChildCount() > 0) { 1547 for (UIComponent kid : getChildren()) { 1548 if (!(kid instanceof UIColumn)) { 1549 continue; 1550 } 1551 if (kid.getChildCount() > 0) { 1552 for (UIComponent grandkid : kid.getChildren()) { 1553 if (grandkid.visitTree(context, callback)) { 1554 return true; 1555 } 1556 } 1557 } 1558 } 1559 } 1560 1561 } 1562 1563 return false; 1564 } 1565 1566 1567 /** 1568 * <p>Return <code>true</code> if we need to keep the saved 1569 * per-child state information. This will be the case if any of the 1570 * following are true:</p> 1571 * 1572 * <ul> 1573 * 1574 * <li>there are messages queued with severity ERROR or FATAL.</li> 1575 * 1576 * <li>this <code>UIData</code> instance is nested inside of another 1577 * <code>UIData</code> instance</li> 1578 * 1579 * </ul> 1580 * 1581 * @param context {@link FacesContext} for the current request 1582 */ 1583 private boolean keepSaved(FacesContext context) { 1584 1585 return (contextHasErrorMessages(context) || isNestedWithinUIData()); 1586 1587 } 1588 1589 1590 private Boolean isNestedWithinUIData() { 1591 if (isNested == null) { 1592 UIComponent parent = this; 1593 while (null != (parent = parent.getParent())) { 1594 if (parent instanceof UIData) { 1595 isNested = Boolean.TRUE; 1596 break; 1597 } 1598 } 1599 if (isNested == null) { 1600 isNested = Boolean.FALSE; 1601 } 1602 return isNested; 1603 } else { 1604 return isNested; 1605 } 1606 } 1607 1608 1609 private boolean contextHasErrorMessages(FacesContext context) { 1610 1611 FacesMessage.Severity sev = context.getMaximumSeverity(); 1612 return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0)); 1613 1614 } 1615 1616 1617 /** 1618 * <p>Restore state information for all descendant components, as described 1619 * for <code>setRowIndex()</code>.</p> 1620 */ 1621 private void restoreDescendantState() { 1622 1623 FacesContext context = getFacesContext(); 1624 if (getChildCount() > 0) { 1625 for (UIComponent kid : getChildren()) { 1626 if (kid instanceof UIColumn) { 1627 restoreDescendantState(kid, context); 1628 } 1629 } 1630 } 1631 1632 } 1633 1634 1635 /** 1636 * <p>Restore state information for the specified component and its 1637 * descendants.</p> 1638 * 1639 * @param component Component for which to restore state information 1640 * @param context {@link FacesContext} for the current request 1641 */ 1642 private void restoreDescendantState(UIComponent component, 1643 FacesContext context) { 1644 1645 // Reset the client identifier for this component 1646 String id = component.getId(); 1647 component.setId(id); // Forces client id to be reset 1648 Map<String, SavedState> saved = (Map<String,SavedState>) 1649 getStateHelper().get(PropertyKeys.saved); 1650 // Restore state for this component (if it is a EditableValueHolder) 1651 if (component instanceof EditableValueHolder) { 1652 EditableValueHolder input = (EditableValueHolder) component; 1653 String clientId = component.getClientId(context); 1654 1655 SavedState state = saved.get(clientId); 1656 if (state == null) { 1657 state = new SavedState(); 1658 } 1659 input.setValue(state.getValue()); 1660 input.setValid(state.isValid()); 1661 input.setSubmittedValue(state.getSubmittedValue()); 1662 // This *must* be set after the call to setValue(), since 1663 // calling setValue() always resets "localValueSet" to true. 1664 input.setLocalValueSet(state.isLocalValueSet()); 1665 } else if (component instanceof UIForm) { 1666 UIForm form = (UIForm) component; 1667 String clientId = component.getClientId(context); 1668 SavedState state = saved.get(clientId); 1669 if (state == null) { 1670 state = new SavedState(); 1671 } 1672 form.setSubmitted(state.getSubmitted()); 1673 state.setSubmitted(form.isSubmitted()); 1674 } 1675 1676 // Restore state for children of this component 1677 if (component.getChildCount() > 0) { 1678 for (UIComponent kid : component.getChildren()) { 1679 restoreDescendantState(kid, context); 1680 } 1681 } 1682 1683 // Restore state for facets of this component 1684 if (component.getFacetCount() > 0) { 1685 for (UIComponent facet : component.getFacets().values()) { 1686 restoreDescendantState(facet, context); 1687 } 1688 } 1689 1690 } 1691 1692 1693 /** 1694 * <p>Save state information for all descendant components, as described for 1695 * <code>setRowIndex()</code>.</p> 1696 */ 1697 private void saveDescendantState() { 1698 1699 FacesContext context = getFacesContext(); 1700 if (getChildCount() > 0) { 1701 for (UIComponent kid : getChildren()) { 1702 if (kid instanceof UIColumn) { 1703 saveDescendantState(kid, context); 1704 } 1705 } 1706 } 1707 1708 } 1709 1710 1711 /** 1712 * <p>Save state information for the specified component and its 1713 * descendants.</p> 1714 * 1715 * @param component Component for which to save state information 1716 * @param context {@link FacesContext} for the current request 1717 */ 1718 private void saveDescendantState(UIComponent component, 1719 FacesContext context) { 1720 1721 // Save state for this component (if it is a EditableValueHolder) 1722 Map<String, SavedState> saved = (Map<String, SavedState>) 1723 getStateHelper().get(PropertyKeys.saved); 1724 if (component instanceof EditableValueHolder) { 1725 EditableValueHolder input = (EditableValueHolder) component; 1726 SavedState state = null; 1727 String clientId = component.getClientId(context); 1728 if (saved == null) { 1729 state = new SavedState(); 1730 getStateHelper().put(PropertyKeys.saved, clientId, state); 1731 } 1732 if (state == null) { 1733 state = saved.get(clientId); 1734 if (state == null) { 1735 state = new SavedState(); 1736 //saved.put(clientId, state); 1737 getStateHelper().put(PropertyKeys.saved, clientId, state); 1738 } 1739 } 1740 state.setValue(input.getLocalValue()); 1741 state.setValid(input.isValid()); 1742 state.setSubmittedValue(input.getSubmittedValue()); 1743 state.setLocalValueSet(input.isLocalValueSet()); 1744 } else if (component instanceof UIForm) { 1745 UIForm form = (UIForm) component; 1746 String clientId = component.getClientId(context); 1747 SavedState state = null; 1748 if (saved == null) { 1749 state = new SavedState(); 1750 getStateHelper().put(PropertyKeys.saved, clientId, state); 1751 } 1752 if (state == null) { 1753 state = saved.get(clientId); 1754 if (state == null) { 1755 state = new SavedState(); 1756 //saved.put(clientId, state); 1757 getStateHelper().put(PropertyKeys.saved, clientId, state); 1758 } 1759 } 1760 state.setSubmitted(form.isSubmitted()); 1761 } 1762 1763 // Save state for children of this component 1764 if (component.getChildCount() > 0) { 1765 for (UIComponent uiComponent : component.getChildren()) { 1766 saveDescendantState(uiComponent, context); 1767 } 1768 } 1769 1770 // Save state for facets of this component 1771 if (component.getFacetCount() > 0) { 1772 for (UIComponent facet : component.getFacets().values()) { 1773 saveDescendantState(facet, context); 1774 } 1775 } 1776 1777 } 1778 1779 } 1780 @SuppressWarnings({"SerializableHasSerializationMethods", 1781 "NonSerializableFieldInSerializableClass"}) 1782 class SavedState implements Serializable { 1783 1784 private static final long serialVersionUID = 2920252657338389849L; 1785 private Object submittedValue; 1786 private boolean submitted; 1787 1788 Object getSubmittedValue() { 1789 return (this.submittedValue); 1790 } 1791 1792 void setSubmittedValue(Object submittedValue) { 1793 this.submittedValue = submittedValue; 1794 } 1795 1796 private boolean valid = true; 1797 1798 boolean isValid() { 1799 return (this.valid); 1800 } 1801 1802 void setValid(boolean valid) { 1803 this.valid = valid; 1804 } 1805 1806 private Object value; 1807 1808 Object getValue() { 1809 return (this.value); 1810 } 1811 1812 public void setValue(Object value) { 1813 this.value = value; 1814 } 1815 1816 private boolean localValueSet; 1817 1818 boolean isLocalValueSet() { 1819 return (this.localValueSet); 1820 } 1821 1822 public void setLocalValueSet(boolean localValueSet) { 1823 this.localValueSet = localValueSet; 1824 } 1825 1826 public boolean getSubmitted() { 1827 return this.submitted; 1828 } 1829 1830 public void setSubmitted(boolean submitted) { 1831 this.submitted = submitted; 1832 } 1833 1834 public String toString() { 1835 return ("submittedValue: " + submittedValue + 1836 " value: " + value + 1837 " localValueSet: " + localValueSet); 1838 } 1839 1840 } 1841 1842 1843 // Private class to wrap an event with a row index 1844 class WrapperEvent extends FacesEvent { 1845 1846 1847 public WrapperEvent(UIComponent component, FacesEvent event, int rowIndex) { 1848 super(component); 1849 this.event = event; 1850 this.rowIndex = rowIndex; 1851 } 1852 1853 private FacesEvent event = null; 1854 private int rowIndex = -1; 1855 1856 public FacesEvent getFacesEvent() { 1857 return (this.event); 1858 } 1859 1860 public int getRowIndex() { 1861 return (this.rowIndex); 1862 } 1863 1864 public PhaseId getPhaseId() { 1865 return (this.event.getPhaseId()); 1866 } 1867 1868 public void setPhaseId(PhaseId phaseId) { 1869 this.event.setPhaseId(phaseId); 1870 } 1871 1872 public boolean isAppropriateListener(FacesListener listener) { 1873 return (false); 1874 } 1875 1876 public void processListener(FacesListener listener) { 1877 throw new IllegalStateException(); 1878 } 1879 1880 1881 }