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

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

Go to download

Jakarta Faces defines an MVC framework for building user interfaces for web applications, including UI components, state management, event handing, input validation, page navigation, and support for internationalization and accessibility.

There is a newer version: 4.1.0
Show newest version
/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package jakarta.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 jakarta.el.ValueExpression;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.faces.FacesException;
import jakarta.faces.application.Application;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.application.StateManager;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.el.ValueBinding;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.FacesListener;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PostValidateEvent;
import jakarta.faces.event.PreValidateEvent;
import jakarta.faces.model.ArrayDataModel;
import jakarta.faces.model.CollectionDataModel;
import jakarta.faces.model.DataModel;
import jakarta.faces.model.FacesDataModel;
import jakarta.faces.model.IterableDataModel;
import jakarta.faces.model.ListDataModel;
import jakarta.faces.model.ResultDataModel;
import jakarta.faces.model.ResultSetDataModel;
import jakarta.faces.model.ScalarDataModel;
import jakarta.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 jakarta.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 = "jakarta.faces.Data"; /** *

* The standard component family for this component. *

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

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

*/ public UIData() { super(); setRendererType("jakarta.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(jakarta.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 be of type * {@link DataModel}, or a type that can be adapted into a {@link DataModel}. UIData will automatically * adapt the following types: *

*
    *
  • Arrays
  • *
  • java.util.List
  • *
  • java.sql.ResultSet
  • *
  • jakarta.servlet.jsp.jstl.sql.Result *
  • java.util.Collection
  • *
*

* 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, jakarta.el.ValueExpression)}. */ @Deprecated @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": 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 { pushComponentToEL(context, compositeParent); callback.invokeContextCallback(context, this); return true; } catch (Exception e) { throw new FacesException(e); } finally { popComponentFromEL(context); } } // check the facets, if any, of UIData if (getFacetCount() > 0) { for (UIComponent c : getFacets().values()) { if (clientId.equals(c.getClientId(context))) { callback.invokeContextCallback(context, c); return true; } } } // check column level facets, if any if (getChildCount() > 0) { for (UIComponent column : 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 (getChildCount() > 0) { for (UIComponent column : getChildren()) { if (column instanceof UIColumn) { if (column.invokeOnComponent(context, clientId, callback)) { return true; } } } } int lastSep, newRow, savedRowIndex = getRowIndex(); 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)) { try { 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.parseInt(clientId.substring(preRowIndexSep, postRowIndexSep)); } catch (NumberFormatException ex) { // PENDING(edburns): I18N String message = "Trying to extract rowIndex from clientId \'" + clientId + "\' " + ex.getMessage(); throw new NumberFormatException(message); } setRowIndex(newRow); if (isRowAvailable()) { found = super.invokeOnComponent(context, clientId, callback); } } } } } catch (FacesException fe) { throw fe; } catch (NumberFormatException e) { throw new FacesException(e); } finally { 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 Jakarta Server 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 jakarta.faces.event.PreValidateEvent * @see jakarta.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 Jakarta Expression Language 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 = 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 (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) { 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 submittedValue; } void setSubmittedValue(Object submittedValue) { this.submittedValue = submittedValue; } private boolean valid = true; boolean isValid() { return valid; } void setValid(boolean valid) { this.valid = valid; } private Object value; Object getValue() { return value; } public void setValue(Object value) { this.value = value; } private boolean localValueSet; boolean isLocalValueSet() { return localValueSet; } public void setLocalValueSet(boolean localValueSet) { this.localValueSet = localValueSet; } public boolean getSubmitted() { return 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 event; } public int getRowIndex() { return rowIndex; } @Override public PhaseId getPhaseId() { return event.getPhaseId(); } @Override public void setPhaseId(PhaseId phaseId) { event.setPhaseId(phaseId); } @Override public boolean isAppropriateListener(FacesListener listener) { return false; } @Override public void processListener(FacesListener listener) { throw new IllegalStateException(); } }