javax.faces.component.UIData Maven / Gradle / Ivy
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* https://javaserverfaces.dev.java.net/CDDL.html or
* legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* [Name of File] [ver.__] [Date]
*
* Copyright 2005 Sun Microsystems Inc. All Rights Reserved
*/
package javax.faces.component;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
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.model.ArrayDataModel;
import javax.faces.model.DataModel;
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;
import java.io.IOException;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* 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 {
// ------------------------------------------------------ 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
/**
* The first row number (zero-relative) to be displayed.
*/
private int first = 0;
private boolean firstSet = false;
/**
* 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;
/**
*
The zero-relative index of the current row number, or -1 for
* no current row association.
*/
private int rowIndex = -1;
/**
* The number of rows to display, or zero for all remaining
* rows in the table.
*/
private int rows = 0;
private boolean rowsSet = false;
/**
* 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.
*/
@SuppressWarnings({"CollectionWithoutInitialCapacity"})
private Map saved = new HashMap();
/**
* The local value of this {@link UIComponent}.
*/
private Object value = null;
/**
* The request scope attribute under which the data object for the
* current row will be exposed when iterating.
*/
private String var = null;
// -------------------------------------------------------------- Properties
public String getFamily() {
return (COMPONENT_FAMILY);
}
/**
* Return the zero-relative row number of the first row to be
* displayed.
*/
public int getFirst() {
if (this.firstSet) {
return (this.first);
}
ValueExpression ve = getValueExpression("first");
if (ve != null) {
Integer value;
try {
value = (Integer) ve.getValue(getFacesContext().getELContext());
}
catch (ELException e) {
throw new FacesException(e);
}
if (null == value) {
return first;
}
return (value.intValue());
} else {
return (this.first);
}
}
/**
* 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));
}
this.first = first;
this.firstSet = true;
}
/**
* Return the footer facet of this component (if any). A convenience
* method for getFacet("footer")
.
*/
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")
.
*/
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
.
*
* @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.
*
* @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.
*
* @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.
*
* @throws FacesException if an error occurs getting the row index
*/
public int getRowIndex() {
return (this.rowIndex);
}
/**
* 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.
*
*
* - 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) {
// Save current state for the previous row index
saveDescendantState();
// Update to the new row index
this.rowIndex = rowIndex;
DataModel localModel = getDataModel();
localModel.setRowIndex(rowIndex);
// Clear or expose the current row data as a request scope attribute
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();
}
/**
* 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.
*/
public int getRows() {
if (this.rowsSet) {
return (this.rows);
}
ValueExpression ve = getValueExpression("rows");
if (ve != null) {
Integer value;
try {
value = (Integer) ve.getValue(getFacesContext().getELContext());
}
catch (ELException e) {
throw new FacesException(e);
}
if (null == value) {
return rows;
}
return (value.intValue());
} else {
return (this.rows);
}
}
/**
* 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));
}
this.rows = rows;
this.rowsSet = true;
}
/**
* 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.
*/
public String getVar() {
return (this.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) {
this.var = var;
}
// ----------------------------------------------------- StateHolder Methods
private Object[] values;
public Object saveState(FacesContext context) {
if (values == null) {
values = new Object[9];
}
values[0] = super.saveState(context);
values[1] = new Integer(first);
values[2] = firstSet ? Boolean.TRUE : Boolean.FALSE;
values[3] = new Integer(rowIndex);
values[4] = new Integer(rows);
values[5] = rowsSet ? Boolean.TRUE : Boolean.FALSE;
values[6] = saved;
values[7] = value;
values[8] = var;
return (values);
}
public void restoreState(FacesContext context, Object state) {
values = (Object[]) state;
super.restoreState(context, values[0]);
first = ((Integer) values[1]).intValue();
firstSet = ((Boolean) values[2]).booleanValue();
rowIndex = ((Integer) values[3]).intValue();
rows = ((Integer) values[4]).intValue();
rowsSet = ((Boolean) values[5]).booleanValue();
saved = TypedCollections.dynamicallyCastMap((Map) values[6], String.class, SavedState.class);
value = values[7];
var = (String) values[8];
}
/**
* 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
* javax.servlet.jsp.jstl.sql.Result
*
* All other types will be adapted using the {@link ScalarDataModel}
* class, which will treat the object as a single row of data.
*/
public Object getValue() {
if (this.value != null) {
return (this.value);
}
ValueExpression ve = getValueExpression("value");
if (ve != null) {
try {
return (ve.getValue(getFacesContext().getELContext()));
}
catch (ELException e) {
throw new FacesException(e);
}
} else {
return (null);
}
}
/**
* 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);
this.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)}.
*/
public void setValueBinding(String name, ValueBinding binding) {
if ("value".equals(name)) {
setDataModel(null);
} else if ("var".equals(name) || "rowIndex".equals(name)) {
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
*/
public void setValueExpression(String name, ValueExpression binding) {
if ("value".equals(name)) {
this.model = null;
} else if ("var".equals(name) || "rowIndex".equals(name)) {
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
*/
public String getClientId(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
String baseClientId = super.getClientId(context);
if (rowIndex >= 0) {
return (baseClientId + NamingContainer.SEPARATOR_CHAR + rowIndex);
} else {
return (baseClientId);
}
}
/**
* 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.
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}.
*
* @since 1.2
* @throws NullPointerException {@inheritDoc}
* @throws FacesException {@inheritDoc} Also throws
* FacesException
if any exception is thrown when deriving
* the rowIndex from the argument clientId
.
*
*/
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 {
callback.invokeContextCallback(context, this);
return true;
}
catch (Exception e) {
throw new FacesException(e);
}
}
int lastSep, newRow, savedRowIndex = this.getRowIndex();
try {
// If we need to strip out the rowIndex from our id
// PENDING(edburns): is this safe with respect to I18N?
if (myId.endsWith(String.valueOf(savedRowIndex))) {
lastSep = myId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
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(NamingContainer.SEPARATOR_CHAR,
myId.length()))) {
// Check the length
if (++preRowIndexSep < clientId.length()) {
if (-1 != (postRowIndexSep =
clientId.indexOf(NamingContainer.SEPARATOR_CHAR,
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 (Exception 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
*/
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
*/
public void broadcast(FacesEvent event)
throws AbortProcessingException {
if (!(event instanceof WrapperEvent)) {
super.broadcast(event);
return;
}
// Set up the correct context and fire our wrapped event
WrapperEvent revent = (WrapperEvent) event;
if (isNestedWithinUIData()) {
setDataModel(null);
}
int oldRowIndex = getRowIndex();
setRowIndex(revent.getRowIndex());
FacesEvent rowEvent = revent.getFacesEvent();
rowEvent.getComponent().broadcast(rowEvent);
setRowIndex(oldRowIndex);
return;
}
/**
* 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
*/
public void encodeBegin(FacesContext context) throws IOException {
setDataModel(null); // re-evaluate even with server-side state saving
if (!keepSaved(context)) {
//noinspection CollectionWithoutInitialCapacity
saved = new HashMap();
}
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
*/
public void processDecodes(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
setDataModel(null); // Re-evaluate even with server-side state saving
if (null == saved || !keepSaved(context)) {
//noinspection CollectionWithoutInitialCapacity
saved = new HashMap(); // We don't need saved state here
}
iterate(context, PhaseId.APPLY_REQUEST_VALUES);
decode(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
*/
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
if (isNestedWithinUIData()) {
setDataModel(null);
}
iterate(context, PhaseId.PROCESS_VALIDATIONS);
// This is not a EditableValueHolder, so no further processing is required
}
/**
* 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
*/
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
if (isNestedWithinUIData()) {
setDataModel(null);
}
iterate(context, PhaseId.UPDATE_MODEL_VALUES);
// This is not a EditableValueHolder, so no further processing is required
}
// --------------------------------------------------------- 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.
*/
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 {
setDataModel(new ScalarDataModel(current));
}
return (model);
}
/**
* 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
/**
* 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);
Iterator facets = getFacets().keySet().iterator();
while (facets.hasNext()) {
UIComponent facet = getFacets().get(facets.next());
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();
}
}
// Process each facet of our child UIColumn components exactly once
setRowIndex(-1);
Iterator columns = getChildren().iterator();
while (columns.hasNext()) {
UIComponent column = (UIComponent) columns.next();
if (!(column instanceof UIColumn)) {
continue;
}
if (!column.isRendered()) {
continue;
}
Iterator columnFacets = column.getFacets().keySet().iterator();
while (columnFacets.hasNext()) {
UIComponent columnFacet = column.getFacets().get(columnFacets.next());
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)
Iterator kids = getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
if (!(kid instanceof UIColumn)) {
continue;
}
Iterator grandkids = kid.getChildren().iterator();
while (grandkids.hasNext()) {
UIComponent grandkid = (UIComponent) grandkids.next();
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);
}
/**
* 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:
*
*
*
* - any of the saved state corresponds to components that have
* messages that must be displayed
*
* - this
UIData
instance is nested inside of another
* UIData
instance
*
*
*
* @param context {@link FacesContext} for the current request
*/
private boolean keepSaved(FacesContext context) {
Iterator clientIds = saved.keySet().iterator();
while (clientIds.hasNext()) {
String clientId = (String) clientIds.next();
Iterator messages = context.getMessages(clientId);
while (messages.hasNext()) {
FacesMessage message = (FacesMessage) messages.next();
if (message.getSeverity().compareTo(FacesMessage.SEVERITY_ERROR)
>= 0) {
return (true);
}
}
}
return (isNestedWithinUIData());
}
private boolean isNestedWithinUIData() {
UIComponent parent = this;
while (null != (parent = parent.getParent())) {
if (parent instanceof UIData) {
return true;
}
}
return (false);
}
/**
* Restore state information for all descendant components, as described
* for setRowIndex()
.
*/
private void restoreDescendantState() {
FacesContext context = getFacesContext();
Iterator kids = getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
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
// 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.get(clientId);
if (state == null) {
state = new SavedState();
}
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());
}
// Restore state for children of this component
Iterator kids = component.getChildren().iterator();
while (kids.hasNext()) {
restoreDescendantState((UIComponent) kids.next(), context);
}
// Restore state for facets of this component
Iterator facetNames = component.getFacets().keySet().iterator();
while (facetNames.hasNext()) {
UIComponent c = component.getFacet( (String) facetNames.next() );
if (c!=null)
restoreDescendantState(c, context);
}
}
/**
* Save state information for all descendant components, as described
* for setRowIndex()
.
*/
private void saveDescendantState() {
FacesContext context = getFacesContext();
Iterator kids = getChildren().iterator();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
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)
if (component instanceof EditableValueHolder) {
EditableValueHolder input = (EditableValueHolder) component;
String clientId = component.getClientId(context);
SavedState state = saved.get(clientId);
if (state == null) {
state = new SavedState();
saved.put(clientId, state);
}
state.setValue(input.getLocalValue());
state.setValid(input.isValid());
state.setSubmittedValue(input.getSubmittedValue());
state.setLocalValueSet(input.isLocalValueSet());
}
// Save state for children of this component
Iterator kids = component.getChildren().iterator();
while (kids.hasNext()) {
saveDescendantState((UIComponent) kids.next(), context);
}
// Save state for facets of this component
Iterator facetNames = component.getFacets().keySet().iterator();
while (facetNames.hasNext()) {
UIComponent c = component.getFacet( (String) facetNames.next() );
if (c!=null)
saveDescendantState(c, context);
}
}
}
// ------------------------------------------------------------- Private Classes
// Private class to represent saved state information
@SuppressWarnings({"SerializableHasSerializationMethods",
"NonSerializableFieldInSerializableClass"})
class SavedState implements Serializable {
private static final long serialVersionUID = 2920252657338389849L;
private Object submittedValue;
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 String toString() {
return ("submittedValue: " + submittedValue +
" value: " + value +
" localValueSet: " + localValueSet);
}
}
// Private class to wrap an event with a row index
class WrapperEvent extends FacesEvent {
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);
}
public PhaseId getPhaseId() {
return (this.event.getPhaseId());
}
public void setPhaseId(PhaseId phaseId) {
this.event.setPhaseId(phaseId);
}
public boolean isAppropriateListener(FacesListener listener) {
return (false);
}
public void processListener(FacesListener listener) {
throw new IllegalStateException();
}
}