All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.faces.component.UIData Maven / Gradle / Ivy

The newest version!
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package javax.faces.component;

import java.io.IOException;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.el.ValueExpression;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.CDI;
import javax.enterprise.util.AnnotationLiteral;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.application.StateManager;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.event.PostValidateEvent;
import javax.faces.event.PreValidateEvent;
import javax.faces.model.ArrayDataModel;
import javax.faces.model.CollectionDataModel;
import javax.faces.model.DataModel;
import javax.faces.model.FacesDataModel;
import javax.faces.model.IterableDataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.ResultDataModel;
import javax.faces.model.ResultSetDataModel;
import javax.faces.model.ScalarDataModel;
import javax.servlet.jsp.jstl.sql.Result;


// ------------------------------------------------------------- Private Classes
// Private class to represent saved state information

/**
 * 

UIData is a {@link UIComponent} that * supports data binding to a collection of data objects represented by * a {@link DataModel} instance, which is the current value of this * component itself (typically established via a {@link * ValueExpression}). During iterative processing over the rows of data * in the data model, the object for the current row is exposed as a * request attribute under the key specified by the var * property.

Only children of type {@link UIColumn} should * be processed by renderers associated with this component.

*

By default, the rendererType property is set to * javax.faces.Table. This value can be changed by calling * the setRendererType() method.

*/ public class UIData extends UIComponentBase implements NamingContainer, UniqueIdVendor { // ------------------------------------------------------ Manifest Constants /** *

The standard component type for this component.

*/ public static final String COMPONENT_TYPE = "javax.faces.Data"; /** *

The standard component family for this component.

*/ public static final String COMPONENT_FAMILY = "javax.faces.Data"; // ------------------------------------------------------------ Constructors /** *

Create a new {@link UIData} instance with default property * values.

*/ public UIData() { super(); setRendererType("javax.faces.Table"); } // ------------------------------------------------------ Instance Variables /** * Properties that are tracked by state saving. */ enum PropertyKeys { /** *

The first row number (zero-relative) to be displayed.

*/ first, /** *

The zero-relative index of the current row number, or -1 for no * current row association.

*/ rowIndex, /** *

The number of rows to display, or zero for all remaining rows in the * table.

*/ rows, /** *

This map contains SavedState instances for each * descendant component, keyed by the client identifier of the descendant. * Because descendant client identifiers will contain the * rowIndex value of the parent, per-row state information is * actually preserved.

*/ saved, /** *

The local value of this {@link UIComponent}.

*/ value, /** *

The request scope attribute under which the data object for the * current row will be exposed when iterating.

*/ var, /** *

Last id vended by {@link UIData#createUniqueId(javax.faces.context.FacesContext, String)}.

*/ lastId, /** * */ rowStatePreserved } /** *

The {@link DataModel} associated with this component, lazily * instantiated if requested. This object is not part of the saved and * restored state of the component.

*/ private DataModel model = null; /** *

During iteration through the rows of this table, This ivar is used to * store the previous "var" value for this instance. When the row iteration * is complete, this value is restored to the request map. */ private Object oldVar; /** *

Holds the base client ID that will be used to generate per-row * client IDs (this will be null if this UIData is nested within another).

* *

This is not part of the component state.

*/ private String baseClientId = null; /** *

Length of the cached baseClientId plus one for * the {@link UINamingContainer#getSeparatorChar}.

* *

This is not part of the component state.

*/ private int baseClientIdLength; /** *

StringBuilder used to build per-row client IDs.

* *

This is not part of the component state.

*/ private StringBuilder clientIdBuilder = null; /** *

Flag indicating whether or not this UIData instance is nested * within another UIData instance

* *

This is not part of the component state.

*/ private Boolean isNested = null; private Map _rowDeltaStates = new HashMap<>(); private Map _rowTransientStates = new HashMap<>(); private Object _initialDescendantFullComponentState = null; // -------------------------------------------------------------- Properties @Override public String getFamily() { return (COMPONENT_FAMILY); } /** *

Return the zero-relative row number of the first row to be * displayed.

* * @return the row number. */ public int getFirst() { return (Integer) getStateHelper().eval(PropertyKeys.first, 0); } /** *

Set the zero-relative row number of the first row to be * displayed.

* * @param first New first row number * * @throws IllegalArgumentException if first is negative */ public void setFirst(int first) { if (first < 0) { throw new IllegalArgumentException(String.valueOf(first)); } getStateHelper().put(PropertyKeys.first, first); } /** *

Return the footer facet of this component (if any). A convenience * method for getFacet("footer").

* * @return the footer facet. */ public UIComponent getFooter() { return getFacet("footer"); } /** *

Set the footer facet of this component. A convenience method for * getFacets().put("footer", footer).

* * @param footer the new footer facet * * @throws NullPointerException if footer is null */ public void setFooter(UIComponent footer) { getFacets().put("footer", footer); } /** *

Return the header facet of this component (if any). A convenience * method for getFacet("header").

* * @return the header facet. */ public UIComponent getHeader() { return getFacet("header"); } /** *

Set the header facet of this component. A convenience method for * getFacets().put("header", header).

* * @param header the new header facet * * @throws NullPointerException if header is null */ public void setHeader(UIComponent header) { getFacets().put("header", header); } /** *

Return a flag indicating whether there is rowData * available at the current rowIndex. If no * wrappedData is available, return false.

* * @return whether the row is available. * * @throws FacesException if an error occurs getting the row availability */ public boolean isRowAvailable() { return (getDataModel().isRowAvailable()); } /** *

Return the number of rows in the underlying data model. If the number * of available rows is unknown, return -1.

* * @return the row count. * @throws FacesException if an error occurs getting the row count */ public int getRowCount() { return (getDataModel().getRowCount()); } /** *

Return the data object representing the data for the currently * selected row index, if any.

* * @return the row data. * * @throws FacesException if an error occurs getting the row data * @throws IllegalArgumentException if now row data is available at the * currently specified row index */ public Object getRowData() { return (getDataModel().getRowData()); } /** *

Return the zero-relative index of the currently selected row. If we * are not currently positioned on a row, return -1. This property is * not enabled for value binding expressions.

* * @return the row index. * * @throws FacesException if an error occurs getting the row index */ public int getRowIndex() { return (Integer) getStateHelper().eval(PropertyKeys.rowIndex, -1); } /** *

Set the zero * relative index of the current row, or -1 to indicate that no row * is currently selected, by implementing the following algorithm. * It is possible to set the row index at a value for which the * underlying data collection does not contain any row data. * Therefore, callers may use the isRowAvailable() * method to detect whether row data will be available for use by * the getRowData() method.

*

To support transient state among * descendents, please consult the specification for {@link * #setRowStatePreserved}, which details the requirements * for setRowIndex() when the * rowStatePreserved JavaBeans property is set * to true.

*
    *
  • Save current state information for all descendant components (as * described below). *
  • Store the new row index, and pass it on to the {@link DataModel} * associated with this {@link UIData} instance.
  • *
  • If the new rowIndex value is -1: *
      *
    • If the var property is not null, * remove the corresponding request scope attribute (if any).
    • *
    • Reset the state information for all descendant components * (as described below).
    • *
  • *
  • If the new rowIndex value is not -1: *
      *
    • If the var property is not null, call * getRowData() and expose the resulting data object * as a request scope attribute whose key is the var * property value.
    • *
    • Reset the state information for all descendant components * (as described below). *
  • *
* *

To save current state information for all descendant components, * {@link UIData} must maintain per-row information for each descendant * as follows:

*
    *
  • If the descendant is an instance of EditableValueHolder, save * the state of its localValue property.
  • *
  • If the descendant is an instance of EditableValueHolder, * save the state of the localValueSet property.
  • *
  • If the descendant is an instance of EditableValueHolder, save * the state of the valid property.
  • *
  • If the descendant is an instance of EditableValueHolder, * save the state of the submittedValue property.
  • *
* *

To restore current state information for all descendant components, * {@link UIData} must reference its previously stored information for the * current rowIndex and call setters for each descendant * as follows:

*
    *
  • If the descendant is an instance of EditableValueHolder, * restore the value property.
  • *
  • If the descendant is an instance of EditableValueHolder, * restore the state of the localValueSet property.
  • *
  • If the descendant is an instance of EditableValueHolder, * restore the state of the valid property.
  • *
  • If the descendant is an instance of EditableValueHolder, * restore the state of the submittedValue property.
  • *
* * @param rowIndex The new row index value, or -1 for no associated row * * @throws FacesException if an error occurs setting the row index * @throws IllegalArgumentException if rowIndex * is less than -1 */ public void setRowIndex(int rowIndex) { if (isRowStatePreserved()) { setRowIndexRowStatePreserved(rowIndex); } else { setRowIndexWithoutRowStatePreserved(rowIndex); } } private void setRowIndexWithoutRowStatePreserved(int rowIndex){ // Save current state for the previous row index saveDescendantState(); // Update to the new row index //this.rowIndex = rowIndex; getStateHelper().put(PropertyKeys.rowIndex, rowIndex); DataModel localModel = getDataModel(); localModel.setRowIndex(rowIndex); // if rowIndex is -1, clear the cache if (rowIndex == -1) { setDataModel(null); } // Clear or expose the current row data as a request scope attribute String var = (String) getStateHelper().get(PropertyKeys.var); if (var != null) { Map requestMap = getFacesContext().getExternalContext().getRequestMap(); if (rowIndex == -1) { oldVar = requestMap.remove(var); } else if (isRowAvailable()) { requestMap.put(var, getRowData()); } else { requestMap.remove(var); if (null != oldVar) { requestMap.put(var, oldVar); oldVar = null; } } } // Reset current state information for the new row index restoreDescendantState(); } private void setRowIndexRowStatePreserved(int rowIndex) { if (rowIndex < -1) { throw new IllegalArgumentException("rowIndex is less than -1"); } if (getRowIndex() == rowIndex) { return; } FacesContext facesContext = getFacesContext(); if (_initialDescendantFullComponentState != null) { //Just save the row Map sm = saveFullDescendantComponentStates(facesContext, null, getChildren().iterator(), false); if (sm != null && !sm.isEmpty()) { _rowDeltaStates.put(getContainerClientId(facesContext), sm); } if (getRowIndex() != -1) { _rowTransientStates.put(getContainerClientId(facesContext), saveTransientDescendantComponentStates(facesContext, null, getChildren().iterator(), false)); } } // Update to the new row index //this.rowIndex = rowIndex; getStateHelper().put(PropertyKeys.rowIndex, rowIndex); DataModel localModel = getDataModel(); localModel.setRowIndex(rowIndex); // if rowIndex is -1, clear the cache if (rowIndex == -1) { setDataModel(null); } // Clear or expose the current row data as a request scope attribute String var = (String) getStateHelper().get(PropertyKeys.var); if (var != null) { Map requestMap = getFacesContext().getExternalContext().getRequestMap(); if (rowIndex == -1) { oldVar = requestMap.remove(var); } else if (isRowAvailable()) { requestMap.put(var, getRowData()); } else { requestMap.remove(var); if (null != oldVar) { requestMap.put(var, oldVar); oldVar = null; } } } if (_initialDescendantFullComponentState != null) { Object rowState = _rowDeltaStates.get(getContainerClientId(facesContext)); if (rowState == null) { //Restore as original restoreFullDescendantComponentStates(facesContext, getChildren().iterator(), _initialDescendantFullComponentState, false); } else { //Restore first original and then delta restoreFullDescendantComponentDeltaStates(facesContext, getChildren().iterator(), rowState, _initialDescendantFullComponentState, false); } if (getRowIndex() == -1) { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false); } else { rowState = _rowTransientStates.get(getContainerClientId(facesContext)); if (rowState == null) { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), null, false); } else { restoreTransientDescendantComponentStates(facesContext, getChildren().iterator(), (Map) rowState, false); } } } } /** *

Return the number of rows to be displayed, or zero for all remaining * rows in the table. The default value of this property is zero.

* * @return the number of rows. */ public int getRows() { return (Integer) getStateHelper().eval(PropertyKeys.rows, 0); } /** *

Set the number of rows to be displayed, or zero for all remaining rows * in the table.

* * @param rows New number of rows * * @throws IllegalArgumentException if rows is negative */ public void setRows(int rows) { if (rows < 0) { throw new IllegalArgumentException(String.valueOf(rows)); } getStateHelper().put(PropertyKeys.rows, rows); } /** *

Return the request-scope attribute under which the data object for the * current row will be exposed when iterating. This property is * not enabled for value binding expressions.

* * @return he request-scope attribute. */ public String getVar() { return (String) getStateHelper().get(PropertyKeys.var); } /** *

Set the request-scope attribute under which the data object for the * current row wil be exposed when iterating.

* * @param var The new request-scope attribute name */ public void setVar(String var) { getStateHelper().put(PropertyKeys.var, var); } /** *

Return the value of the * rowStatePreserved JavaBeans property. See * {@link #setRowStatePreserved}.

* * @return the value of the rowStatePreserved. * * @since 2.1 */ public boolean isRowStatePreserved() { Boolean b = (Boolean) getStateHelper().get(PropertyKeys.rowStatePreserved); return b == null ? false : b.booleanValue(); } /** *

If this property is set to * true, the UIData must take steps to * ensure that modifications to its iterated children will be * preserved on a per-row basis. This allows applications to modify * component properties, such as the style-class, for a specific * row, rather than having such modifications apply to all rows.

*
*

To accomplish this, UIData must call {@link * StateHolder#saveState} and {@link * TransientStateHolder#saveTransientState} on its children to * capture their state on exiting each row. When re-entering the * row, {@link StateHolder#restoreState} and {@link * TransientStateHolder#restoreTransientState} must be called in * order to reinitialize the children to the correct state for the * new row. All of this action must take place during the * processing of {@link #setRowIndex}.

*

Users should consider enabling this feature for cases where * it is necessary to modify properties of UIData's * children in a row-specific way. Note, however, that row-level * state saving/restoring does add overhead. As such, this feature * should be used judiciously.

*
* * @param preserveComponentState the flag if the state should be preserved. * * @since 2.1 */ public void setRowStatePreserved(boolean preserveComponentState) { getStateHelper().put(PropertyKeys.rowStatePreserved, preserveComponentState); } // ----------------------------------------------------- StateHolder Methods /** *

Return the value of the UIData. * This value must either be of type {@link DataModel}, or a type that can be adapted * into a {@link DataModel}. UIData will automatically * adapt the following types:

* *
    *
  • java.util.List
  • *
  • Arrays
  • *
  • java.sql.ResultSet
  • *
  • javax.servlet.jsp.jstl.sql.Result *
  • java.util.Collection
  • *
  • java.lang.Iterable
  • *
  • java.util.Map
  • *
  • Types for which a suitable {@link DataModel} has been registered * via {@link FacesDataModel}
  • *
* *

All other types will be adapted using the {@link ScalarDataModel} * class, which will treat the object as a single row of data.

* * @return the object for the value. */ public Object getValue() { return getStateHelper().eval(PropertyKeys.value); } /** *

Set the value of the UIData. This value must either be * be of type {@link DataModel}, or a type that can be adapted into a {@link * DataModel}.

* * @param value the new value */ public void setValue(Object value) { setDataModel(null); getStateHelper().put(PropertyKeys.value, value); } // ----------------------------------------------------- UIComponent Methods /** *

If "name" is something other than "value", "var", or "rowIndex", rely * on the superclass conversion from ValueBinding to * ValueExpression.

* * @param name Name of the attribute or property for which to set a * {@link ValueBinding} * @param binding The {@link ValueBinding} to set, or null to * remove any currently set {@link ValueBinding} * * @throws IllegalArgumentException if name is one of * id, parent, * var, or rowIndex * @throws NullPointerException if name is null * @deprecated This has been replaced by {@link #setValueExpression(java.lang.String, *javax.el.ValueExpression)}. */ @Override public void setValueBinding(String name, ValueBinding binding) { if (null != name) { switch (name) { case "value": setDataModel(null); break; case "var": case "rowIndex": throw new IllegalArgumentException(); } } super.setValueBinding(name, binding); } /** *

Set the {@link ValueExpression} used to calculate the value for the * specified attribute or property name, if any. In addition, if a {@link * ValueExpression} is set for the value property, remove any * synthesized {@link DataModel} for the data previously bound to this * component.

* * @param name Name of the attribute or property for which to set a * {@link ValueExpression} * @param binding The {@link ValueExpression} to set, or null * to remove any currently set {@link ValueExpression} * * @throws IllegalArgumentException if name is one of * id, parent, * var, or rowIndex * @throws NullPointerException if name is null * @since 1.2 */ @Override public void setValueExpression(String name, ValueExpression binding) { if (null != name) { switch (name) { case "value": this.model = null; break; case "var": case "rowIndex": throw new IllegalArgumentException(); } } super.setValueExpression(name, binding); } /** *

Return a client identifier for this component that includes the * current value of the rowIndex property, if it is not set to * -1. This implies that multiple calls to getClientId() may * return different results, but ensures that child components can * themselves generate row-specific client identifiers (since {@link UIData} * is a {@link NamingContainer}).

* * @throws NullPointerException if context is null */ @Override public String getClientId(FacesContext context) { if (context == null) { throw new NullPointerException(); } // If baseClientId and clientIdBuilder are both null, this is the // first time that getClientId() has been called. // If we're not nested within another UIData, then: // - create a new StringBuilder assigned to clientIdBuilder containing // our client ID. // - toString() the builder - this result will be our baseClientId // for the duration of the component // - append UINamingContainer.getSeparatorChar() to the builder // If we are nested within another UIData, then: // - create an empty StringBuilder that will be used to build // this instance's ID if (baseClientId == null && clientIdBuilder == null) { if (!isNestedWithinIterator()) { clientIdBuilder = new StringBuilder(super.getClientId(context)); baseClientId = clientIdBuilder.toString(); baseClientIdLength = (baseClientId.length() + 1); clientIdBuilder.append(UINamingContainer.getSeparatorChar(context)); clientIdBuilder.setLength(baseClientIdLength); } else { clientIdBuilder = new StringBuilder(); } } int rowIndex = getRowIndex(); if (rowIndex >= 0) { String cid; if (!isNestedWithinIterator()) { // we're not nested, so the clientIdBuilder is already // primed with clientID + // UINamingContainer.getSeparatorChar(). Append the // current rowIndex, and toString() the builder. reset // the builder to it's primed state. cid = clientIdBuilder.append(rowIndex).toString(); clientIdBuilder.setLength(baseClientIdLength); } else { // we're nested, so we have to build the ID from scratch // each time. Reuse the same clientIdBuilder instance // for each call by resetting the length to 0 after // the ID has been computed. cid = clientIdBuilder.append(super.getClientId(context)) .append(UINamingContainer.getSeparatorChar(context)).append(rowIndex) .toString(); clientIdBuilder.setLength(0); } return (cid); } else { if (!isNestedWithinIterator()) { // Not nested and no row available, so just return our baseClientId return (baseClientId); } else { // nested and no row available, return the result of getClientId(). // this is necessary as the client ID will reflect the row that // this table represents return super.getClientId(context); } } } /** *

Override behavior from {@link * UIComponentBase#invokeOnComponent} to provide special care for * positioning the data properly before finding the component and * invoking the callback on it. If the argument * clientId is equal to this.getClientId() * simply invoke the contextCallback, passing the * context argument and this as arguments, and * return true. If the argument clientId * is not equal to this.getClientId(), inspect each of * the facet children of this UIData instance and for * each one, compare its clientId with the argument * clientId. If there is a match, invoke the * contextCallback, passing the context * argument and this as arguments, and return * true. Otherwise, attempt to extract a rowIndex from * the clientId. For example, if the argument * clientId was form:data:3:customerHeader * the rowIndex would be 3. Let this value be called * newIndex. The current rowIndex of this instance must * be saved aside and restored before returning in all cases, * regardless of the outcome of the search or if any exceptions are * thrown in the process.

* *

The implementation of this method must never return true * if setting the rowIndex of this instance to be equal to * newIndex causes this instance to return false * from {@link #isRowAvailable}.

* * @throws NullPointerException {@inheritDoc} * @throws FacesException {@inheritDoc} Also throws FacesException * if any exception is thrown when deriving the * rowIndex from the argument clientId. * @since 1.2 */ @Override public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { if (null == context || null == clientId || null == callback) { throw new NullPointerException(); } String myId = super.getClientId(context); boolean found = false; if (clientId.equals(myId)) { try { this.pushComponentToEL(context, compositeParent); callback.invokeContextCallback(context, this); return true; } catch (Exception e) { throw new FacesException(e); } finally { this.popComponentFromEL(context); } } // check the facets, if any, of UIData if (this.getFacetCount() > 0) { for (UIComponent c : this.getFacets().values()) { if (clientId.equals(c.getClientId(context))) { callback.invokeContextCallback(context, c); return true; } } } // check column level facets, if any if (this.getChildCount() > 0) { for (UIComponent column : this.getChildren()) { if (column instanceof UIColumn) { if (column.getFacetCount() > 0) { for (UIComponent facet : column.getFacets().values()) { if (facet.invokeOnComponent(context, clientId, callback)) { return true; } } } } } } /* * Check if we are looking for a component that is part of the * actual skeleton. */ if (this.getChildCount() > 0) { for (UIComponent column : this.getChildren()) { if (column instanceof UIColumn) { if (column.invokeOnComponent(context, clientId, callback)) { return true; } } } } int lastSep, newRow, savedRowIndex = this.getRowIndex(); try { char sepChar = UINamingContainer.getSeparatorChar(context); // If we need to strip out the rowIndex from our id // PENDING(edburns): is this safe with respect to I18N? if (myId.endsWith(sepChar + Integer.toString(savedRowIndex, 10))) { lastSep = myId.lastIndexOf(sepChar); assert (-1 != lastSep); myId = myId.substring(0, lastSep); } // myId will be something like form:outerData for a non-nested table, // and form:outerData:3:data for a nested table. // clientId will be something like form:outerData:3:outerColumn // for a non-nested table. clientId will be something like // outerData:3:data:3:input for a nested table. if (clientId.startsWith(myId)) { int preRowIndexSep, postRowIndexSep; if (-1 != (preRowIndexSep = clientId.indexOf(sepChar, myId.length()))) { // Check the length if (++preRowIndexSep < clientId.length()) { if (-1 != (postRowIndexSep = clientId.indexOf(sepChar, preRowIndexSep + 1))) { try { newRow = Integer .valueOf(clientId.substring(preRowIndexSep, postRowIndexSep)) .intValue(); } catch (NumberFormatException ex) { // PENDING(edburns): I18N String message = "Trying to extract rowIndex from clientId \'" + clientId + "\' " + ex.getMessage(); throw new NumberFormatException(message); } this.setRowIndex(newRow); if (this.isRowAvailable()) { found = super.invokeOnComponent(context, clientId, callback); } } } } } } catch (FacesException fe) { throw fe; } catch (NumberFormatException e) { throw new FacesException(e); } finally { this.setRowIndex(savedRowIndex); } return found; } /** *

Override the default {@link UIComponentBase#queueEvent} processing to * wrap any queued events in a wrapper so that we can reset the current row * index in broadcast().

* * @param event {@link FacesEvent} to be queued * * @throws IllegalStateException if this component is not a descendant of a * {@link UIViewRoot} * @throws NullPointerException if event is null */ @Override public void queueEvent(FacesEvent event) { super.queueEvent(new WrapperEvent(this, event, getRowIndex())); } /** *

Override the default {@link UIComponentBase#broadcast} processing to * unwrap any wrapped {@link FacesEvent} and reset the current row index, * before the event is actually broadcast. For events that we did not wrap * (in queueEvent()), default processing will occur.

* * @param event The {@link FacesEvent} to be broadcast * * @throws AbortProcessingException Signal the JavaServer Faces * implementation that no further * processing on the current event should * be performed * @throws IllegalArgumentException if the implementation class of this * {@link FacesEvent} is not supported by * this component * @throws NullPointerException if event is null */ @Override public void broadcast(FacesEvent event) throws AbortProcessingException { if (!(event instanceof WrapperEvent)) { super.broadcast(event); return; } FacesContext context = event.getFacesContext(); // Set up the correct context and fire our wrapped event WrapperEvent revent = (WrapperEvent) event; if (isNestedWithinIterator()) { setDataModel(null); } int oldRowIndex = getRowIndex(); setRowIndex(revent.getRowIndex()); FacesEvent rowEvent = revent.getFacesEvent(); UIComponent source = rowEvent.getComponent(); UIComponent compositeParent = null; try { if (!UIComponent.isCompositeComponent(source)) { compositeParent = UIComponent.getCompositeComponentParent(source); } if (compositeParent != null) { compositeParent.pushComponentToEL(context, null); } source.pushComponentToEL(context, null); source.broadcast(rowEvent); } finally { source.popComponentFromEL(context); if (compositeParent != null) { compositeParent.popComponentFromEL(context); } } setRowIndex(oldRowIndex); } /** *

In addition to the default behavior, ensure that any saved per-row * state for our child input components is discarded unless it is needed to * rerender the current page with errors. * * @param context FacesContext for the current request * * @throws IOException if an input/output error occurs while * rendering * @throws NullPointerException if context is null */ @Override public void encodeBegin(FacesContext context) throws IOException { preEncode(context); super.encodeBegin(context); } /** *

Override the default {@link UIComponentBase#processDecodes} processing * to perform the following steps.

  • If the rendered * property of this {@link UIComponent} is false, skip further * processing.
  • Set the current rowIndex to -1.
  • *
  • Call the processDecodes() method of all facets of this * {@link UIData}, in the order determined by a call to * getFacets().keySet().iterator().
  • Call the * processDecodes() method of all facets of the {@link * UIColumn} children of this {@link UIData}.
  • Iterate over the set * of rows that were included when this component was rendered (i.e. those * defined by the first and rows properties), * performing the following processing for each row:
    • Set the * current rowIndex to the appropriate value for this row.
    • *
    • If isRowAvailable() returns true, iterate * over the children components of each {@link UIColumn} child of this * {@link UIData} component, calling the processDecodes() * method for each such child.
  • Set the current * rowIndex to -1.
  • Call the decode() * method of this component.
  • If a RuntimeException is * thrown during decode processing, call {@link FacesContext#renderResponse} * and re-throw the exception.
* * @param context {@link FacesContext} for the current request * * @throws NullPointerException if context is null */ @Override public void processDecodes(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (!isRendered()) { return; } pushComponentToEL(context, this); preDecode(context); iterate(context, PhaseId.APPLY_REQUEST_VALUES); decode(context); popComponentFromEL(context); } /** *

Override the default {@link UIComponentBase#processValidators} * processing to perform the following steps.

  • If the * rendered property of this {@link UIComponent} is * false, skip further processing.
  • Set the current * rowIndex to -1.
  • Call the processValidators() * method of all facets of this {@link UIData}, in the order determined by a * call to getFacets().keySet().iterator().
  • Call the * processValidators() method of all facets of the {@link * UIColumn} children of this {@link UIData}.
  • Iterate over the set * of rows that were included when this component was rendered (i.e. those * defined by the first and rows properties), * performing the following processing for each row:
    • Set the * current rowIndex to the appropriate value for this row.
    • *
    • If isRowAvailable() returns true, iterate * over the children components of each {@link UIColumn} child of this * {@link UIData} component, calling the processValidators() * method for each such child.
  • Set the current * rowIndex to -1.
* * @param context {@link FacesContext} for the current request * @throws NullPointerException if context is null * @see javax.faces.event.PreValidateEvent * @see javax.faces.event.PostValidateEvent */ @Override public void processValidators(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (!isRendered()) { return; } pushComponentToEL(context, this); Application app = context.getApplication(); app.publishEvent(context, PreValidateEvent.class, this); preValidate(context); iterate(context, PhaseId.PROCESS_VALIDATIONS); app.publishEvent(context, PostValidateEvent.class, this); popComponentFromEL(context); } /** *

Override the default {@link UIComponentBase#processUpdates} * processing to perform the following steps.

*
    *
  • If the rendered property of this {@link UIComponent} * is false, skip further processing.
  • *
  • Set the current rowIndex to -1.
  • *
  • Call the processUpdates() method of all facets * of this {@link UIData}, in the order determined * by a call to getFacets().keySet().iterator().
  • *
  • Call the processUpdates() method of all facets * of the {@link UIColumn} children of this {@link UIData}.
  • *
  • Iterate over the set of rows that were included when this * component was rendered (i.e. those defined by the first * and rows properties), performing the following * processing for each row: *
      *
    • Set the current rowIndex to the appropriate * value for this row.
    • *
    • If isRowAvailable() returns true, * iterate over the children components of each {@link UIColumn} * child of this {@link UIData} component, calling the * processUpdates() method for each such child.
    • *
  • *
  • Set the current rowIndex to -1.
  • *
* * @param context {@link FacesContext} for the current request * * @throws NullPointerException if context is null */ @Override public void processUpdates(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (!isRendered()) { return; } pushComponentToEL(context, this); preUpdate(context); iterate(context, PhaseId.UPDATE_MODEL_VALUES); popComponentFromEL(context); // This is not a EditableValueHolder, so no further processing is required } @Override public String createUniqueId(FacesContext context, String seed) { Integer i = (Integer) getStateHelper().get(PropertyKeys.lastId); int lastId = ((i != null) ? i : 0); getStateHelper().put(PropertyKeys.lastId, ++lastId); return UIViewRoot.UNIQUE_ID_PREFIX + (seed == null ? lastId : seed); } /** *

Override the behavior * in {@link UIComponent#visitTree} to handle iteration * correctly.

* *
*

If the {@link UIComponent#isVisitable} method of this instance * returns false, take no action and return.

*

Call {@link UIComponent#pushComponentToEL} and * invoke the visit callback on this UIData instance as * described in {@link UIComponent#visitTree}. Let the result of * the invoctaion be visitResult. If visitResult * is {@link VisitResult#COMPLETE}, take no further action and * return true. Otherwise, determine if we need to * visit our children. The default implementation calls {@link * VisitContext#getSubtreeIdsToVisit} passing this as * the argument. If the result of that call is non-empty, let * doVisitChildren be true. If * doVisitChildren is true and * visitResult is {@link VisitResult#ACCEPT}, take the * following action.

*
    *
  • If this component has facets, call {@link * UIComponent#getFacets} on this instance and invoke the * values() method. For each * UIComponent in the returned Map, * call {@link UIComponent#visitTree}.

  • *
  • *
    *

    If this component has children, for each * UIColumn child:

    * *

    Call {@link VisitContext#invokeVisitCallback} on that UIColumn instance. * If such a call returns true, terminate visiting and return true from this method.

    * *

    If the child UIColumn has facets, call * {@link UIComponent#visitTree} on each one.

    * *

    Take no action on non-UIColumn children.

    * *
    *
  • * *
  • *
    * *

    Save aside the result of a call to {@link * #getRowIndex}.

    *

    For each child component of this UIData that is * also an instance of {@link UIColumn}, *

    *

    Iterate over the rows.

    *
    *
      *
    • Let rowsToProcess be the return from {@link * #getRows}.

    • *
    • Let rowIndex be the return from {@link * #getFirst} - 1.

    • *
    • While the number of rows processed is less than * rowsToProcess, take the following actions.

      *

      Call {@link #setRowIndex}, passing the current row index.

      *

      If {@link #isRowAvailable} returns false, take no * further action and return false.

      * *

      Call {@link * UIComponent#visitTree} on each of the children of this * UIColumn instance.

      *
    • *
    *
  • *
*

Call {@link #popComponentFromEL} and restore the saved row * index with a call to {@link #setRowIndex}.

*

Return false to allow the visiting to * continue.

*
* * @param context the VisitContext that provides * context for performing the visit. * * @param callback the callback to be invoked for each node * encountered in the visit. * @throws NullPointerException if any of the parameters are * null. * */ @Override public boolean visitTree(VisitContext context, VisitCallback callback) { // First check to see whether we are visitable. If not // short-circuit out of this subtree, though allow the // visit to proceed through to other subtrees. if (!isVisitable(context)) return false; FacesContext facesContext = context.getFacesContext(); // NOTE: that the visitRows local will be obsolete once the // appropriate visit hints have been added to the API boolean visitRows = requiresRowIteration(context); // Clear out the row index is one is set so that // we start from a clean slate. int oldRowIndex = -1; if (visitRows) { oldRowIndex = getRowIndex(); setRowIndex(-1); } // Push ourselves to EL pushComponentToEL(facesContext, null); try { // Visit ourselves. Note that we delegate to the // VisitContext to actually perform the visit. VisitResult result = context.invokeVisitCallback(this, callback); // If the visit is complete, short-circuit out and end the visit if (result == VisitResult.COMPLETE) return true; // Visit children, short-circuiting as necessary // NOTE: that the visitRows parameter will be obsolete once the // appropriate visit hints have been added to the API if ((result == VisitResult.ACCEPT) && doVisitChildren(context, visitRows)) { // First visit facets // NOTE: that the visitRows parameter will be obsolete once the // appropriate visit hints have been added to the API if (visitFacets(context, callback, visitRows)) return true; // Next column facets // NOTE: that the visitRows parameter will be obsolete once the // appropriate visit hints have been added to the API if (visitColumnsAndColumnFacets(context, callback, visitRows)) return true; // And finally, visit rows // NOTE: that the visitRows parameter will be obsolete once the // appropriate visit hints have been added to the API if (visitRows(context, callback, visitRows)) return true; } } finally { // Clean up - pop EL and restore old row index popComponentFromEL(facesContext); if (visitRows) { setRowIndex(oldRowIndex); } } // Return false to allow the visit to continue return false; } /** *

Override the base class method to * take special action if the method is being invoked when {@link * StateManager#IS_BUILDING_INITIAL_STATE} is true * and the rowStatePreserved * JavaBeans property for this instance is true.

* *

The additional action taken is to * traverse the descendents and save their state without regard to * any particular row value.

* * @since 2.1 */ @Override public void markInitialState() { if (isRowStatePreserved()) { if (getFacesContext().getAttributes().containsKey(StateManager.IS_BUILDING_INITIAL_STATE)) { _initialDescendantFullComponentState = saveDescendantInitialComponentStates(getFacesContext(), getChildren().iterator(), false); } } super.markInitialState(); } private void restoreFullDescendantComponentStates(FacesContext facesContext, Iterator childIterator, Object state, boolean restoreChildFacets) { Iterator descendantStateIterator = null; while (childIterator.hasNext()) { if (descendantStateIterator == null && state != null) { descendantStateIterator = ((Collection) state) .iterator(); } UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { Object childState = null; Object descendantState = null; if (descendantStateIterator != null && descendantStateIterator.hasNext()) { Object[] object = descendantStateIterator.next(); childState = object[0]; descendantState = object[1]; } component.clearInitialState(); component.restoreState(facesContext, childState); component.markInitialState(); Iterator childsIterator; if (restoreChildFacets) { childsIterator = component.getFacetsAndChildren(); } else { childsIterator = component.getChildren().iterator(); } restoreFullDescendantComponentStates(facesContext, childsIterator, descendantState, true); } } } private Collection saveDescendantInitialComponentStates(FacesContext facesContext, Iterator childIterator, boolean saveChildFacets) { Collection childStates = null; while (childIterator.hasNext()) { if (childStates == null) { childStates = new ArrayList<>(); } UIComponent child = childIterator.next(); if (!child.isTransient()) { // Add an entry to the collection, being an array of two // elements. The first element is the state of the children // of this component; the second is the state of the current // child itself. Iterator childsIterator; if (saveChildFacets) { childsIterator = child.getFacetsAndChildren(); } else { childsIterator = child.getChildren().iterator(); } Object descendantState = saveDescendantInitialComponentStates( facesContext, childsIterator, true); Object state = child.saveState(facesContext); childStates.add(new Object[] { state, descendantState }); } } return childStates; } private Map saveFullDescendantComponentStates(FacesContext facesContext, Map stateMap, Iterator childIterator, boolean saveChildFacets) { while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { Iterator childsIterator; if (saveChildFacets) { childsIterator = child.getFacetsAndChildren(); } else { childsIterator = child.getChildren().iterator(); } stateMap = saveFullDescendantComponentStates(facesContext, stateMap, childsIterator, true); Object state = child.saveState(facesContext); if (state != null) { if (stateMap == null) { stateMap = new HashMap<>(); } stateMap.put(child.getClientId(facesContext), state); } } } return stateMap; } private void restoreFullDescendantComponentDeltaStates(FacesContext facesContext, Iterator childIterator, Object state, Object initialState, boolean restoreChildFacets) { Map descendantStateIterator = null; Iterator descendantFullStateIterator = null; while (childIterator.hasNext()) { if (descendantStateIterator == null && state != null) { descendantStateIterator = (Map) state; } if (descendantFullStateIterator == null && initialState != null) { descendantFullStateIterator = ((Collection) initialState).iterator(); } UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { Object childInitialState = null; Object descendantInitialState = null; Object childState = null; if (descendantStateIterator != null && descendantStateIterator.containsKey(component.getClientId(facesContext))) { //Object[] object = (Object[]) descendantStateIterator.get(component.getClientId(facesContext)); //childState = object[0]; childState = descendantStateIterator.get(component.getClientId(facesContext)); } if (descendantFullStateIterator != null && descendantFullStateIterator.hasNext()) { Object[] object = (Object[]) descendantFullStateIterator.next(); childInitialState = object[0]; descendantInitialState = object[1]; } component.clearInitialState(); if (childInitialState != null) { component.restoreState(facesContext, childInitialState); component.markInitialState(); component.restoreState(facesContext, childState); } else { component.restoreState(facesContext, childState); component.markInitialState(); } Iterator childsIterator; if (restoreChildFacets) { childsIterator = component.getFacetsAndChildren(); } else { childsIterator = component.getChildren().iterator(); } restoreFullDescendantComponentDeltaStates(facesContext, childsIterator, state, descendantInitialState , true); } } } private void restoreTransientDescendantComponentStates(FacesContext facesContext, Iterator childIterator, Map state, boolean restoreChildFacets) { while (childIterator.hasNext()) { UIComponent component = childIterator.next(); // reset the client id (see spec 3.1.6) component.setId(component.getId()); if (!component.isTransient()) { component.restoreTransientState(facesContext, (state == null) ? null : state.get(component.getClientId(facesContext))); Iterator childsIterator; if (restoreChildFacets) { childsIterator = component.getFacetsAndChildren(); } else { childsIterator = component.getChildren().iterator(); } restoreTransientDescendantComponentStates(facesContext, childsIterator, state, true); } } } private Map saveTransientDescendantComponentStates(FacesContext facesContext, Map childStates, Iterator childIterator, boolean saveChildFacets) { while (childIterator.hasNext()) { UIComponent child = childIterator.next(); if (!child.isTransient()) { Iterator childsIterator; if (saveChildFacets) { childsIterator = child.getFacetsAndChildren(); } else { childsIterator = child.getChildren().iterator(); } childStates = saveTransientDescendantComponentStates(facesContext, childStates, childsIterator, true); Object state = child.saveTransientState(facesContext); if (state != null) { if (childStates == null) { childStates = new HashMap<>(); } childStates.put(child.getClientId(facesContext), state); } } } return childStates; } @Override public void restoreState(FacesContext context, Object state) { if (state == null) { return; } Object values[] = (Object[]) state; super.restoreState(context, values[0]); Object restoredRowStates = UIComponentBase.restoreAttachedState(context, values[1]); if (restoredRowStates == null) { if (!_rowDeltaStates.isEmpty()) { _rowDeltaStates.clear(); } } else { _rowDeltaStates = (Map) restoredRowStates; } } private void resetClientIds(UIComponent component) { Iterator iterator = component.getFacetsAndChildren(); while(iterator.hasNext()) { UIComponent child = iterator.next(); resetClientIds(child); child.setId(child.getId()); } } @Override public Object saveState(FacesContext context) { resetClientIds(this); if (initialStateMarked()) { Object superState = super.saveState(context); if (superState == null && _rowDeltaStates.isEmpty()) { return null; } else { Object values[] = null; Object attachedState = UIComponentBase.saveAttachedState(context, _rowDeltaStates); if (superState != null || attachedState != null) { values = new Object[] { superState, attachedState }; } return values; } } else { Object values[] = new Object[2]; values[0] = super.saveState(context); values[1] = UIComponentBase.saveAttachedState(context, _rowDeltaStates); return values; } } // --------------------------------------------------------- Protected Methods /** *

Return the internal {@link DataModel} object representing the data * objects that we will iterate over in this component's rendering.

* *

If the model has been cached by a previous call to {@link * #setDataModel}, return it. Otherwise call {@link #getValue}. If the * result is null, create an empty {@link ListDataModel} and return it. If * the result is an instance of {@link DataModel}, return it. Otherwise, * adapt the result as described in {@link #getValue} and return it.

* * @return the data model. */ protected DataModel getDataModel() { // Return any previously cached DataModel instance if (this.model != null) { return (model); } // Synthesize a DataModel around our current value if possible Object current = getValue(); if (current == null) { setDataModel(new ListDataModel(Collections.EMPTY_LIST)); } else if (current instanceof DataModel) { setDataModel((DataModel) current); } else if (current instanceof List) { setDataModel(new ListDataModel((List) current)); } else if (Object[].class.isAssignableFrom(current.getClass())) { setDataModel(new ArrayDataModel((Object[]) current)); } else if (current instanceof ResultSet) { setDataModel(new ResultSetDataModel((ResultSet) current)); } else if (current instanceof Result) { setDataModel(new ResultDataModel((Result) current)); } else if (current instanceof Collection) { setDataModel(new CollectionDataModel((Collection) current)); } else if (current instanceof Iterable) { setDataModel(new IterableDataModel<>((Iterable) current)); } else if (current instanceof Map) { setDataModel(new IterableDataModel<>(((Map) current).entrySet())); } else { DataModel dataModel = createDataModel(current.getClass()); if (dataModel != null) { dataModel.setWrappedData(current); setDataModel(dataModel); } else { setDataModel(new ScalarDataModel(current)); } } return (model); } @SuppressWarnings("all") static private class FacesDataModelAnnotationLiteral extends AnnotationLiteral implements FacesDataModel { private static final long serialVersionUID = 1L; /** * Stores the forClass attribute. */ private final Class forClass; public FacesDataModelAnnotationLiteral(Class forClass) { this.forClass = forClass; } @Override public Class forClass() { return forClass; } } private DataModel createDataModel(final Class forClass) { List> dataModel = new ArrayList>(1); CDI cdi = CDI.current(); // Scan the map in order, the first class that is a super class or equal to the class for which // we're looking for a DataModel is the closest match, since the Map is sorted on inheritance relation getDataModelClassesMap(cdi).entrySet().stream() .filter(e -> e.getKey().isAssignableFrom(forClass)) .findFirst() .ifPresent( // Get the bean from CDI which is of the class type that we found during annotation scanning // and has the @FacesDataModel annotation, with the "forClass" attribute set to the closest // super class of our target class. e -> dataModel.add( cdi.select( e.getValue(), new FacesDataModelAnnotationLiteral(e.getKey()) ).get()) ); return dataModel.isEmpty()? null : dataModel.get(0); } @SuppressWarnings("unchecked") private Map, Class>> getDataModelClassesMap(CDI cdi) { BeanManager beanManager = cdi.getBeanManager(); // Get the Map with classes for which a custom DataModel implementation is available from CDI Bean bean = beanManager.resolve(beanManager.getBeans("comSunFacesDataModelClassesMap")); Object beanReference = beanManager.getReference(bean, Map.class, beanManager.createCreationalContext(bean)); return (Map, Class>>) beanReference; } /** *

Set the internal DataModel. This UIData instance must * use the given {@link DataModel} as its internal value representation from * now until the next call to setDataModel. If the given * DataModel is null, the internal * DataModel must be reset in a manner so that the next call to * {@link #getDataModel} causes lazy instantion of a newly refreshed * DataModel.

* *

Subclasses might call this method if they either want to restore the * internal DataModel during the Restore View phase or * if they want to explicitly refresh the current DataModel for * the Render Response phase.

* * @param dataModel the new DataModel or null to * cause the model to be refreshed. */ protected void setDataModel(DataModel dataModel) { this.model = dataModel; } // ---------------------------------------------------- Private Methods /** * Called by {@link UIData#visitTree} to determine whether or not the * visitTree implementation should visit the rows of UIData * or by manipulating the row index before visiting the components themselves. * * Once we have the appropriate Visit hints for state saving, this method * will become obsolete. * * @param ctx the FacesContext for the current request * * @return true if row index manipulation is required by the visit to this * UIData instance */ private boolean requiresRowIteration(VisitContext ctx) { return !ctx.getHints().contains(VisitHint.SKIP_ITERATION); } // Perform pre-decode initialization work. Note that this // initialization may be performed either during a normal decode // (ie. processDecodes()) or during a tree visit (ie. visitTree()). private void preDecode(FacesContext context) { setDataModel(null); // Re-evaluate even with server-side state saving Map saved = (Map) getStateHelper().get(PropertyKeys.saved); if (null == saved || !keepSaved(context)) { //noinspection CollectionWithoutInitialCapacity getStateHelper().remove(PropertyKeys.saved); } } // Perform pre-validation initialization work. Note that this // initialization may be performed either during a normal validation // (ie. processValidators()) or during a tree visit (ie. visitTree()). private void preValidate(FacesContext context) { if (isNestedWithinIterator()) { setDataModel(null); } } // Perform pre-update initialization work. Note that this // initialization may be performed either during normal update // (ie. processUpdates()) or during a tree visit (ie. visitTree()). private void preUpdate(FacesContext context) { if (isNestedWithinIterator()) { setDataModel(null); } } // Perform pre-encode initialization work. Note that this // initialization may be performed either during a normal encode // (ie. encodeBegin()) or during a tree visit (ie. visitTree()). private void preEncode(FacesContext context) { setDataModel(null); // re-evaluate even with server-side state saving if (!keepSaved(context)) { ////noinspection CollectionWithoutInitialCapacity //saved = new HashMap(); getStateHelper().remove(PropertyKeys.saved); } } /** *

Perform the appropriate phase-specific processing and per-row * iteration for the specified phase, as follows: *

    *
  • Set the rowIndex property to -1, and process the facets * of this {@link UIData} component exactly once.
  • *
  • Set the rowIndex property to -1, and process the facets * of the {@link UIColumn} children of this {@link UIData} component * exactly once.
  • *
  • Iterate over the relevant rows, based on the first * and row properties, and process the children * of the {@link UIColumn} children of this {@link UIData} component * once per row.
  • *
* * @param context {@link FacesContext} for the current request * @param phaseId {@link PhaseId} of the phase we are currently running */ private void iterate(FacesContext context, PhaseId phaseId) { // Process each facet of this component exactly once setRowIndex(-1); if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { facet.processDecodes(context); } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { facet.processValidators(context); } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { facet.processUpdates(context); } else { throw new IllegalArgumentException(); } } } // collect rendered columns once List renderedColumns = new ArrayList<>(getChildCount()); if (getChildCount() > 0) { for (UIComponent child : getChildren()) { if (child instanceof UIColumn && child.isRendered()) { renderedColumns.add((UIColumn)child); } } } // Process each facet of our child UIColumn components exactly once setRowIndex(-1); for (UIColumn column : renderedColumns) { if (column.getFacetCount() > 0) { for (UIComponent columnFacet : column.getFacets().values()) { if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { columnFacet.processDecodes(context); } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { columnFacet.processValidators(context); } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { columnFacet.processUpdates(context); } else { throw new IllegalArgumentException(); } } } } // Iterate over our UIColumn children, once per row int processed = 0; int rowIndex = getFirst() - 1; int rows = getRows(); while (true) { // Have we processed the requested number of rows? if ((rows > 0) && (++processed > rows)) { break; } // Expose the current row in the specified request attribute setRowIndex(++rowIndex); if (!isRowAvailable()) { break; // Scrolled past the last row } // Perform phase-specific processing as required // on the *children* of the UIColumn (facets have // been done a single time with rowIndex=-1 already) for (UIColumn kid : renderedColumns) { if (kid.getChildCount() > 0) { for (UIComponent grandkid : kid.getChildren()) { if (!grandkid.isRendered()) { continue; } if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { grandkid.processDecodes(context); } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { grandkid.processValidators(context); } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { grandkid.processUpdates(context); } else { throw new IllegalArgumentException(); } } } } } // Clean up after ourselves setRowIndex(-1); } // Tests whether we need to visit our children as part of // a tree visit private boolean doVisitChildren(VisitContext context, boolean visitRows) { // Just need to check whether there are any ids under this // subtree. Make sure row index is cleared out since // getSubtreeIdsToVisit() needs our row-less client id. if (visitRows) { setRowIndex(-1); } Collection idsToVisit = context.getSubtreeIdsToVisit(this); assert(idsToVisit != null); // All ids or non-empty collection means we need to visit our children. return (!idsToVisit.isEmpty()); } // // Performs pre-phase initialization before visiting children // // (if necessary). // private void preVisitChildren(VisitContext visitContext) { // // // If EXECUTE_LIFECYCLE hint is set, we need to do // // lifecycle-related initialization before visiting children // if (visitContext.getHints().contains(VisitHint.EXECUTE_LIFECYCLE)) { // FacesContext facesContext = visitContext.getFacesContext(); // PhaseId phaseId = facesContext.getCurrentPhaseId(); // // if (phaseId == PhaseId.APPLY_REQUEST_VALUES) // preDecode(facesContext); // else if (phaseId == PhaseId.PROCESS_VALIDATIONS) // preValidate(facesContext); // else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) // preUpdate(facesContext); // else if (phaseId == PhaseId.RENDER_RESPONSE) // preEncode(facesContext); // } // } // Visit each facet of this component exactly once. private boolean visitFacets(VisitContext context, VisitCallback callback, boolean visitRows) { if (visitRows) { setRowIndex(-1); } if (getFacetCount() > 0) { for (UIComponent facet : getFacets().values()) { if (facet.visitTree(context, callback)) return true; } } return false; } // Visit each UIColumn and any facets it may have defined exactly once private boolean visitColumnsAndColumnFacets(VisitContext context, VisitCallback callback, boolean visitRows) { if (visitRows) { setRowIndex(-1); } if (getChildCount() > 0) { for (UIComponent column : getChildren()) { if (column instanceof UIColumn) { VisitResult result = context.invokeVisitCallback(column, callback); // visit the column directly if (result == VisitResult.COMPLETE) { return true; } if (column.getFacetCount() > 0) { for (UIComponent columnFacet : column.getFacets().values()) { if (columnFacet.visitTree(context, callback)) { return true; } } } } } } return false; } // Visit each column and row private boolean visitRows(VisitContext context, VisitCallback callback, boolean visitRows) { // Iterate over our UIColumn children, once per row int processed = 0; int rowIndex = 0; int rows = 0; if (visitRows) { rowIndex = getFirst() - 1; rows = getRows(); } while (true) { // Have we processed the requested number of rows? if (visitRows) { if ((rows > 0) && (++processed > rows)) { break; } // Expose the current row in the specified request attribute setRowIndex(++rowIndex); if (!isRowAvailable()) { break; // Scrolled past the last row } } // Visit as required on the *children* of the UIColumn // (facets have been done a single time with rowIndex=-1 already) if (getChildCount() > 0) { for (UIComponent kid : getChildren()) { if (!(kid instanceof UIColumn)) { continue; } if (kid.getChildCount() > 0) { for (UIComponent grandkid : kid.getChildren()) { if (grandkid.visitTree(context, callback)) { return true; } } } } } if (!visitRows) { break; } } return false; } /** *

Return true if we need to keep the saved * per-child state information. This will be the case if any of the * following are true:

* *
    * *
  • there are messages queued with severity ERROR or FATAL.
  • * *
  • this UIData instance is nested inside of another * UIData instance
  • * *
* * @param context {@link FacesContext} for the current request */ private boolean keepSaved(FacesContext context) { return (contextHasErrorMessages(context) || isNestedWithinIterator()); } private Boolean isNestedWithinIterator() { if (isNested == null) { UIComponent parent = this; while (null != (parent = parent.getParent())) { if (parent instanceof UIData || parent.getClass().getName().endsWith("UIRepeat")) { isNested = Boolean.TRUE; break; } } if (isNested == null) { isNested = Boolean.FALSE; } return isNested; } else { return isNested; } } private boolean contextHasErrorMessages(FacesContext context) { FacesMessage.Severity sev = context.getMaximumSeverity(); return (sev != null && (FacesMessage.SEVERITY_ERROR.compareTo(sev) >= 0)); } /** *

Restore state information for all descendant components, as described * for setRowIndex().

*/ private void restoreDescendantState() { FacesContext context = getFacesContext(); if (getChildCount() > 0) { for (UIComponent kid : getChildren()) { if (kid instanceof UIColumn) { restoreDescendantState(kid, context); } } } } /** *

Restore state information for the specified component and its * descendants.

* * @param component Component for which to restore state information * @param context {@link FacesContext} for the current request */ private void restoreDescendantState(UIComponent component, FacesContext context) { // Reset the client identifier for this component String id = component.getId(); component.setId(id); // Forces client id to be reset Map saved = (Map) getStateHelper().get(PropertyKeys.saved); // Restore state for this component (if it is a EditableValueHolder) if (component instanceof EditableValueHolder) { EditableValueHolder input = (EditableValueHolder) component; String clientId = component.getClientId(context); SavedState state = (saved == null ? null : saved.get(clientId)); if (state == null) { input.resetValue(); } else { input.setValue(state.getValue()); input.setValid(state.isValid()); input.setSubmittedValue(state.getSubmittedValue()); // This *must* be set after the call to setValue(), since // calling setValue() always resets "localValueSet" to true. input.setLocalValueSet(state.isLocalValueSet()); } } else if (component instanceof UIForm) { UIForm form = (UIForm) component; String clientId = component.getClientId(context); SavedState state = (saved == null ? null : saved.get(clientId)); if (state == null) { // submitted is transient state form.setSubmitted(false); } else { form.setSubmitted(state.getSubmitted()); } } // Restore state for children of this component if (component.getChildCount() > 0) { for (UIComponent kid : component.getChildren()) { restoreDescendantState(kid, context); } } // Restore state for facets of this component if (component.getFacetCount() > 0) { for (UIComponent facet : component.getFacets().values()) { restoreDescendantState(facet, context); } } } /** *

Save state information for all descendant components, as described for * setRowIndex().

*/ private void saveDescendantState() { FacesContext context = getFacesContext(); if (getChildCount() > 0) { for (UIComponent kid : getChildren()) { if (kid instanceof UIColumn) { saveDescendantState(kid, context); } } } } /** *

Save state information for the specified component and its * descendants.

* * @param component Component for which to save state information * @param context {@link FacesContext} for the current request */ private void saveDescendantState(UIComponent component, FacesContext context) { // Save state for this component (if it is a EditableValueHolder) Map saved = (Map) getStateHelper().get(PropertyKeys.saved); if (component instanceof EditableValueHolder) { EditableValueHolder input = (EditableValueHolder) component; SavedState state = null; String clientId = component.getClientId(context); if (saved == null) { state = new SavedState(); } if (state == null) { state = saved.get(clientId); if (state == null) { state = new SavedState(); } } state.setValue(input.getLocalValue()); state.setValid(input.isValid()); state.setSubmittedValue(input.getSubmittedValue()); state.setLocalValueSet(input.isLocalValueSet()); if (state.hasDeltaState()) { getStateHelper().put(PropertyKeys.saved, clientId, state); } else if (saved != null) { getStateHelper().remove(PropertyKeys.saved, clientId); } } else if (component instanceof UIForm) { UIForm form = (UIForm) component; String clientId = component.getClientId(context); SavedState state = null; if (saved == null) { state = new SavedState(); } if (state == null) { state = saved.get(clientId); if (state == null) { state = new SavedState(); } } state.setSubmitted(form.isSubmitted()); if (state.hasDeltaState()) { getStateHelper().put(PropertyKeys.saved, clientId, state); } else if (saved != null) { getStateHelper().remove(PropertyKeys.saved, clientId); } } // Save state for children of this component if (component.getChildCount() > 0) { for (UIComponent uiComponent : component.getChildren()) { saveDescendantState(uiComponent, context); } } // Save state for facets of this component if (component.getFacetCount() > 0) { for (UIComponent facet : component.getFacets().values()) { saveDescendantState(facet, context); } } } } @SuppressWarnings({"SerializableHasSerializationMethods", "NonSerializableFieldInSerializableClass"}) class SavedState implements Serializable { private static final long serialVersionUID = 2920252657338389849L; private Object submittedValue; private boolean submitted; Object getSubmittedValue() { return (this.submittedValue); } void setSubmittedValue(Object submittedValue) { this.submittedValue = submittedValue; } private boolean valid = true; boolean isValid() { return (this.valid); } void setValid(boolean valid) { this.valid = valid; } private Object value; Object getValue() { return (this.value); } public void setValue(Object value) { this.value = value; } private boolean localValueSet; boolean isLocalValueSet() { return (this.localValueSet); } public void setLocalValueSet(boolean localValueSet) { this.localValueSet = localValueSet; } public boolean getSubmitted() { return this.submitted; } public void setSubmitted(boolean submitted) { this.submitted = submitted; } public boolean hasDeltaState() { return submittedValue != null || value != null || localValueSet || !valid || submitted; } @Override public String toString() { return ("submittedValue: " + submittedValue + " value: " + value + " localValueSet: " + localValueSet); } } // Private class to wrap an event with a row index class WrapperEvent extends FacesEvent { private static final long serialVersionUID = -1064272913195655452L; public WrapperEvent(UIComponent component, FacesEvent event, int rowIndex) { super(component); this.event = event; this.rowIndex = rowIndex; } private FacesEvent event = null; private int rowIndex = -1; public FacesEvent getFacesEvent() { return (this.event); } public int getRowIndex() { return (this.rowIndex); } @Override public PhaseId getPhaseId() { return (this.event.getPhaseId()); } @Override public void setPhaseId(PhaseId phaseId) { this.event.setPhaseId(phaseId); } @Override public boolean isAppropriateListener(FacesListener listener) { return (false); } @Override public void processListener(FacesListener listener) { throw new IllegalStateException(); } }