javax.faces.component.UIData Maven / Gradle / Ivy
Show all versions of myfaces-api Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package javax.faces.component;
import java.io.IOException;
import java.sql.ResultSet;
import java.util.*;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;
import javax.faces.model.*;
import javax.servlet.jsp.jstl.sql.Result;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFacet;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
/**
* Represents an abstraction of a component which has multiple "rows" of data.
*
* The children of this component are expected to be UIColumn components.
*
* Note that the same set of child components are reused to implement each row of the table in turn during
* such phases as apply-request-values and render-response. Altering any of the members of these components
* therefore affects the attribute for every row, except for the following members:
*
* - submittedValue
*
- value (where no EL binding is used)
*
- valid
*
*
* This reuse of the child components also means that it is not possible to save a reference to a component
* during table processing, then access it later and expect it to still represent the same row of the table.
*
* Implementation Notes
*
*
* Each of the UIColumn children of this component has a few component children of its own to render the contents
* of the table cell. However there can be a very large number of rows in a table, so it isn't efficient for the
* UIColumn and all its child objects to be duplicated for each row in the table. Instead the "flyweight" pattern
* is used where a serialized state is held for each row. When setRowIndex is invoked, the UIColumn objects and
* their children serialize their current state then reinitialise themselves from the appropriate saved state.
* This allows a single set of real objects to represent multiple objects which have the same types but potentially
* different internal state. When a row is selected for the first time, its state is set to a clean "initial" state.
* Transient components (including any read-only component) do not save their state; they are just reinitialised as
* required. The state saved/restored when changing rows is not the complete component state, just the fields that
* are expected to vary between rows: "submittedValue", "value", "isValid".
*
*
* Note that a table is a "naming container", so that components within the table have their ids prefixed with the
* id of the table. Actually, when setRowIndex has been called on a table with id of "zzz" the table pretends to
* its children that its ID is "zzz_n" where n is the row index. This means that renderers for child components which
* call component.getClientId automatically get ids of form "zzz_n:childId" thus ensuring that components in
* different rows of the table get different ids.
*
*
* When decoding a submitted page, this class iterates over all its possible rowIndex values, restoring the
* appropriate serialized row state then calling processDecodes on the child components. Because the child
* components (or their renderers) use getClientId to get the request key to look for parameter data, and because
* this object pretends to have a different id per row ("zzz_n") a single child component can decode data from each
* table row in turn without being aware that it is within a table. The table's data model is updated before each
* call to child.processDecodes, so the child decode method can assume that the data model's rowData points to the
* model object associated with the row currently being decoded. Exactly the same process applies for the later
* validation and updateModel phases.
*
*
* When the data model for the table is bound to a backing bean property, and no validation errors have occured
* during processing of a postback, the data model is refetched at the start of the rendering phase (ie after the
* update model phase) so that the contents of the data model can be changed as a result of the latest form
* submission. Because the saved row state must correspond to the elements within the data model, the row state
* must be discarded whenever a new data model is fetched; not doing this would cause all sorts of inconsistency
* issues. This does imply that changing the state of any of the members "submittedValue", "value" or "valid" of
* a component within the table during the invokeApplication phase has no effect on the rendering of the table.
* When a validation error has occurred, a new DataModel is not fetched, and the saved state of the child
* components is not discarded.
*
* see Javadoc of the JSF Specification
* for more information.
*
* @author Manfred Geiler (latest modification by $Author: lu4242 $)
* @version $Revision: 1203044 $ $Date: 2011-11-16 23:22:07 -0500 (Wed, 16 Nov 2011) $
*/
@JSFComponent(defaultRendererType = "javax.faces.Table")
public class UIData extends UIComponentBase
implements NamingContainer
{
public static final String COMPONENT_FAMILY = "javax.faces.Data";
public static final String COMPONENT_TYPE = "javax.faces.Data"; // for unit tests
private static final String FOOTER_FACET_NAME = "footer";
private static final String HEADER_FACET_NAME = "header";
private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
private static final int PROCESS_DECODES = 1;
private static final int PROCESS_VALIDATORS = 2;
private static final int PROCESS_UPDATES = 3;
private static final Object[] LEAF_NO_STATE = new Object[]{null,null};
private int _rowIndex = -1;
private String _var;
// Holds for each row the states of the child components of this UIData.
// Note that only "partial" component state is saved: the component fields
// that are expected to vary between rows.
private Map _rowStates = new HashMap();
/**
* Handle case where this table is nested inside another table.
* See method getDataModel for more details.
*
* Key: parentClientId (aka rowId when nested within a parent table)
* Value: DataModel
*/
private Map _dataModelMap = new HashMap();
// will be set to false if the data should not be refreshed at the beginning of the encode phase
private boolean _isValidChilds = true;
private Object _initialDescendantComponentState = null;
private int _first;
private boolean _firstSet;
private int _rows;
private boolean _rowsSet;
private Object _value;
private static class FacesEventWrapper extends FacesEvent
{
private static final long serialVersionUID = 6648047974065628773L;
private FacesEvent _wrappedFacesEvent;
private int _rowIndex;
public FacesEventWrapper(FacesEvent facesEvent, int rowIndex,
UIData redirectComponent)
{
super(redirectComponent);
_wrappedFacesEvent = facesEvent;
_rowIndex = rowIndex;
}
@Override
public PhaseId getPhaseId()
{
return _wrappedFacesEvent.getPhaseId();
}
@Override
public void setPhaseId(PhaseId phaseId)
{
_wrappedFacesEvent.setPhaseId(phaseId);
}
@Override
public void queue()
{
_wrappedFacesEvent.queue();
}
@Override
public String toString()
{
return _wrappedFacesEvent.toString();
}
@Override
public boolean isAppropriateListener(FacesListener faceslistener)
{
return _wrappedFacesEvent.isAppropriateListener(faceslistener);
}
@Override
public void processListener(FacesListener faceslistener)
{
_wrappedFacesEvent.processListener(faceslistener);
}
public FacesEvent getWrappedFacesEvent()
{
return _wrappedFacesEvent;
}
public int getRowIndex()
{
return _rowIndex;
}
}
private static final DataModel EMPTY_DATA_MODEL = new DataModel()
{
@Override
public boolean isRowAvailable()
{
return false;
}
@Override
public int getRowCount()
{
return 0;
}
@Override
public Object getRowData()
{
throw new IllegalArgumentException();
}
@Override
public int getRowIndex()
{
return -1;
}
@Override
public void setRowIndex(int i)
{
if (i < -1)
throw new IllegalArgumentException();
}
@Override
public Object getWrappedData()
{
return null;
}
@Override
public void setWrappedData(Object obj)
{
if (obj == null)
{
return; //Clearing is allowed
}
throw new UnsupportedOperationException(this.getClass().getName()
+ " UnsupportedOperationException");
}
};
private class EditableValueHolderState
{
private final Object _value;
private final boolean _localValueSet;
private final boolean _valid;
private final Object _submittedValue;
public EditableValueHolderState(EditableValueHolder evh)
{
_value = evh.getLocalValue();
_localValueSet = evh.isLocalValueSet();
_valid = evh.isValid();
_submittedValue = evh.getSubmittedValue();
}
public void restoreState(EditableValueHolder evh)
{
evh.setValue(_value);
evh.setLocalValueSet(_localValueSet);
evh.setValid(_valid);
evh.setSubmittedValue(_submittedValue);
}
}
/**
* Construct an instance of the UIData.
*/
public UIData()
{
setRendererType("javax.faces.Table");
}
@Override
public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
if (context == null || clientId == null || callback == null)
{
throw new NullPointerException();
}
//searching for this component?
boolean returnValue = this.getClientId(context).equals(clientId);
boolean isCachedFacesContext = isCachedFacesContext();
if (!isCachedFacesContext)
{
setCachedFacesContext(context);
}
try
{
if (returnValue)
{
try
{
callback.invokeContextCallback(context, this);
}
catch (Exception e)
{
throw new FacesException(e);
}
return returnValue;
}
//Now Look throught facets on this UIComponent
for (Iterator it = this.getFacets().values().iterator(); !returnValue && it.hasNext();)
{
returnValue = it.next().invokeOnComponent(context, clientId, callback);
}
if (returnValue == true)
{
return returnValue;
}
//Now we have to check if it is searching an inner component
String baseClientId = super.getClientId(context);
// is the component an inner component?
if (clientId.startsWith(baseClientId))
{
// Check if the clientId for the component, which we
// are looking for, has a rowIndex attached
String subId = clientId.substring(baseClientId.length() + 1);
//If the char next to baseClientId is the separator one and
//the subId matches the regular expression
if (clientId.charAt(baseClientId.length()) == NamingContainer.SEPARATOR_CHAR &&
subId.matches("[0-9]+"+NamingContainer.SEPARATOR_CHAR+".*"))
{
String clientRow = subId.substring(0, subId.indexOf(NamingContainer.SEPARATOR_CHAR));
//Now we save the current position
int oldRow = this.getRowIndex();
// try-finally --> make sure, that the old row index is restored
try
{
//The conversion is safe, because its already checked on the
//regular expresion
this.setRowIndex(Integer.parseInt(clientRow));
// check, if the row is available
if (!isRowAvailable())
{
return false;
}
for (Iterator it1 = getChildren().iterator();
!returnValue && it1.hasNext();)
{
//recursive call to find the component
returnValue = it1.next().invokeOnComponent(context, clientId, callback);
}
}
finally
{
//Restore the old position. Doing this prevent
//side effects.
this.setRowIndex(oldRow);
}
}
else
{
// MYFACES-2370: search the component in the childrens' facets too.
// We have to check the childrens' facets here, because in MyFaces
// the rowIndex is not attached to the clientId for the children of
// facets of the UIColumns. However, in RI the rowIndex is
// attached to the clientId of UIColumns' Facets' children.
for (Iterator itChildren = this.getChildren().iterator();
!returnValue && itChildren.hasNext();)
{
UIComponent child = itChildren.next();
if (child instanceof UIColumn && clientId.equals(child.getClientId(context)))
{
try {
callback.invokeContextCallback(context, child);
} catch (Exception e) {
throw new FacesException(e);
}
returnValue = true;
}
// process the child's facets
for (Iterator itChildFacets = child.getFacets().values().iterator();
!returnValue && itChildFacets.hasNext();)
{
//recursive call to find the component
returnValue = itChildFacets.next().invokeOnComponent(context, clientId, callback);
}
}
}
}
}
finally
{
if (!isCachedFacesContext)
{
setCachedFacesContext(null);
}
}
return returnValue;
}
public void setFooter(UIComponent footer)
{
getFacets().put(FOOTER_FACET_NAME, footer);
}
@JSFFacet
public UIComponent getFooter()
{
return (UIComponent) getFacets().get(FOOTER_FACET_NAME);
}
public void setHeader(UIComponent header)
{
getFacets().put(HEADER_FACET_NAME, header);
}
@JSFFacet
public UIComponent getHeader()
{
return (UIComponent) getFacets().get(HEADER_FACET_NAME);
}
public boolean isRowAvailable()
{
return getDataModel().isRowAvailable();
}
public int getRowCount()
{
return getDataModel().getRowCount();
}
public Object getRowData()
{
return getDataModel().getRowData();
}
public int getRowIndex()
{
return _rowIndex;
}
/**
* Set the current row index that methods like getRowData use.
*
* Param rowIndex can be -1, meaning "no row".
*
*
* @param rowIndex
*/
public void setRowIndex(int rowIndex)
{
if (rowIndex < -1)
{
throw new IllegalArgumentException("rowIndex is less than -1");
}
if (_rowIndex == rowIndex)
{
return;
}
FacesContext facesContext = getFacesContext();
if (_rowIndex == -1)
{
if (_initialDescendantComponentState == null)
{
// Create a template that can be used to initialise any row
// that we haven't visited before, ie a "saved state" that can
// be pushed to the "restoreState" method of all the child
// components to set them up to represent a clean row.
_initialDescendantComponentState = saveDescendantComponentStates(this, false, false);
}
}
else
{
// If no initial component state, there are no EditableValueHolder instances,
// and that means there is no state to be saved for the current row, so we can
// skip row state saving code safely.
if (_initialDescendantComponentState != null)
{
// We are currently positioned on some row, and are about to
// move off it, so save the (partial) state of the components
// representing the current row. Later if this row is revisited
// then we can restore this state.
Collection