You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by mb...@apache.org on 2007/03/15 23:28:03 UTC
svn commit: r518786 -
/myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java
Author: mbr
Date: Thu Mar 15 15:28:02 2007
New Revision: 518786
URL: http://svn.apache.org/viewvc?view=rev&rev=518786
Log:
implementation for setValueBinding removed (Base class UIComponentBase delegates call to setValueExpression)
Modified:
myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java
Modified: myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java
URL: http://svn.apache.org/viewvc/myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java?view=diff&rev=518786&r1=518785&r2=518786
==============================================================================
--- myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java (original)
+++ myfaces/core/branches/jsf12/api/src/main/java-templates/javax/faces/component/UIDataTemplate.java Thu Mar 15 15:28:02 2007
@@ -1,995 +1,974 @@
-/*
-* Copyright 2004-2006 The Apache Software Foundation.
-*
-* Licensed 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 javax.faces.context.FacesContext;
-import javax.faces.FacesException;
-import javax.faces.application.FacesMessage;
-import javax.faces.event.FacesEvent;
-import javax.faces.event.AbortProcessingException;
-import javax.faces.event.PhaseId;
-import javax.faces.event.FacesListener;
-import javax.faces.el.ValueBinding;
-import javax.faces.model.DataModel;
-import javax.faces.model.ListDataModel;
-import javax.faces.model.ArrayDataModel;
-import javax.faces.model.ResultSetDataModel;
-import javax.faces.model.ResultDataModel;
-import javax.faces.model.ScalarDataModel;
-import javax.el.ValueExpression;
-import javax.servlet.jsp.jstl.sql.Result;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Collection;
-import java.util.ArrayList;
-import java.util.List;
-import java.io.IOException;
-import java.sql.ResultSet;
-
-/**
- * Represents a component which has multiple "rows" of data.
- * <p>
- * The children of this component are expected to be UIColumn components.
- * <p>
- * 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:
- * <ul>
- * <li>submittedValue
- * <li>value (where no EL binding is used)
- * <li>valid
- * </ul>
- * <p>
- * 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.
- * <h1>
- * Implementation Notes
- * </h1>
- * <p>
- * 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".
- * </p>
- * <p>
- * 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.
- * </p>
- * <p>
- * 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.
- * </p>
- * <p>
- * 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 <i>not</i>
- * fetched, and the saved state of the child components is <i>not</i>
- * discarded.
- * </p>
- * see Javadoc of <a href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF Specification</a> for more.
- *
- * @author Manfred Geiler (latest modification by $Author$)
- * @version $Revision$ $Date$
- */
-abstract public class UIDataTemplate extends UIComponentBase implements NamingContainer
-{
- 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 String _var = null;
- /**/private int _rows;
- /**/private int _first;
- /**/private Object _value;
-
- private int _rowIndex = -1;
-
- // 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.
- * <p>
- * 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;
-
- @Override
- public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
- // not supported yet
- return false;
- }
-
- public void setFooter(UIComponent footer)
- {
- getFacets().put(FOOTER_FACET_NAME, footer);
- }
-
- public UIComponent getFooter()
- {
- return (UIComponent) getFacets().get(FOOTER_FACET_NAME);
- }
-
- public void setHeader(UIComponent header)
- {
- getFacets().put(HEADER_FACET_NAME, header);
- }
-
- 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.
- * <p>
- * Param rowIndex can be -1, meaning "no row".
- * <p>
- * @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(
- getChildren().iterator(), false);
- }
- }
- else
- {
- // 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.
- _rowStates.put(getClientId(facesContext),
- saveDescendantComponentStates(getChildren().iterator(),
- false));
- }
-
- _rowIndex = rowIndex;
-
- DataModel dataModel = getDataModel();
- dataModel.setRowIndex(rowIndex);
-
- String var = _var;
- if (rowIndex == -1)
- {
- if (var != null)
- {
- facesContext.getExternalContext().getRequestMap().remove(var);
- }
- }
- else
- {
- if (var != null)
- {
- if (isRowAvailable())
- {
- Object rowData = dataModel.getRowData();
- facesContext.getExternalContext().getRequestMap().put(var,
- rowData);
- }
- else
- {
- facesContext.getExternalContext().getRequestMap().remove(
- var);
- }
- }
- }
-
- if (_rowIndex == -1)
- {
- // reset components to initial state
- restoreDescendantComponentStates(getChildren().iterator(),
- _initialDescendantComponentState, false);
- }
- else
- {
- Object rowState = _rowStates.get(getClientId(facesContext));
- if (rowState == null)
- {
- // We haven't been positioned on this row before, so just
- // configure the child components of this component with
- // the standard "initial" state
- restoreDescendantComponentStates(getChildren().iterator(),
- _initialDescendantComponentState, false);
- }
- else
- {
- // We have been positioned on this row before, so configure
- // the child components of this component with the (partial)
- // state that was previously saved. Fields not in the
- // partial saved state are left with their original values.
- restoreDescendantComponentStates(getChildren().iterator(),
- rowState, false);
- }
- }
- }
-
- /**
- * Overwrite the state of the child components of this component
- * with data previously saved by method saveDescendantComponentStates.
- * <p>
- * The saved state info only covers those fields that are expected to
- * vary between rows of a table. Other fields are not modified.
- */
- private void restoreDescendantComponentStates(Iterator childIterator,
- Object state, boolean restoreChildFacets)
- {
- Iterator descendantStateIterator = null;
- while (childIterator.hasNext())
- {
- if (descendantStateIterator == null && state != null)
- {
- descendantStateIterator = ((Collection) state).iterator();
- }
- UIComponent component = (UIComponent) 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 = (Object[]) descendantStateIterator.next();
- childState = object[0];
- descendantState = object[1];
- }
- if (component instanceof EditableValueHolder)
- {
- ((EditableValueHolderState) childState)
- .restoreState((EditableValueHolder) component);
- }
- Iterator childsIterator;
- if (restoreChildFacets)
- {
- childsIterator = component.getFacetsAndChildren();
- }
- else
- {
- childsIterator = component.getChildren().iterator();
- }
- restoreDescendantComponentStates(childsIterator, descendantState,
- true);
- }
- }
- }
-
- /**
- * Walk the tree of child components of this UIData, saving the parts of
- * their state that can vary between rows.
- * <p>
- * This is very similar to the process that occurs for normal components
- * when the view is serialized. Transient components are skipped (no
- * state is saved for them).
- * <p>
- * If there are no children then null is returned. If there are one or
- * more children, and all children are transient then an empty collection
- * is returned; this will happen whenever a table contains only read-only
- * components.
- * <p>
- * Otherwise a collection is returned which contains an object for every
- * non-transient child component; that object may itself contain a collection
- * of the state of that child's child components.
- */
- private Object saveDescendantComponentStates(Iterator childIterator,
- boolean saveChildFacets)
- {
- Collection childStates = null;
- while (childIterator.hasNext())
- {
- if (childStates == null)
- {
- childStates = new ArrayList();
- }
- UIComponent child = (UIComponent) 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 = saveDescendantComponentStates(
- childsIterator, true);
- Object state = null;
- if (child instanceof EditableValueHolder)
- {
- state = new EditableValueHolderState(
- (EditableValueHolder) child);
- }
- childStates.add(new Object[] { state, descendantState });
- }
- }
- return childStates;
- }
-
- /**
- * @deprecated Use setValueExpression instead
- */
- @Override
- public void setValueBinding(String name, ValueBinding binding)
- {
- if (name == null)
- {
- throw new NullPointerException("name");
- }
- else if (name.equals("value"))
- {
- _dataModelMap.clear();
- }
- else if (name.equals("var") || name.equals("rowIndex"))
- {
- throw new IllegalArgumentException("name " + name);
- }
- super.setValueBinding(name, binding);
- }
-
- @Override
- public void setValueExpression(String name, ValueExpression binding) {
- if (name == null)
- {
- throw new NullPointerException("name");
- }
- else if (name.equals("value"))
- {
- _dataModelMap.clear();
- }
- else if (name.equals("var") || name.equals("rowIndex"))
- {
- throw new IllegalArgumentException("name " + name);
- }
- super.setValueExpression(name, binding);
- }
-
- @Override
- public String getClientId(FacesContext context)
- {
- String clientId = super.getClientId(context);
- int rowIndex = getRowIndex();
- if (rowIndex == -1)
- {
- return clientId;
- }
- return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
- }
-
- /**
- * Modify events queued for any child components so that the
- * UIData state will be correctly configured before the event's
- * listeners are executed.
- * <p>
- * Child components or their renderers may register events against
- * those child components. When the listener for that event is
- * eventually invoked, it may expect the uidata's rowData and
- * rowIndex to be referring to the same object that caused the
- * event to fire.
- * <p>
- * The original queueEvent call against the child component has been
- * forwarded up the chain of ancestors in the standard way, making
- * it possible here to wrap the event in a new event whose source
- * is <i>this</i> component, not the original one. When the event
- * finally is executed, this component's broadcast method is invoked,
- * which ensures that the UIData is set to be at the correct row
- * before executing the original event.
- */
- @Override
- public void queueEvent(FacesEvent event)
- {
- super.queueEvent(new FacesEventWrapper(event, getRowIndex(), this));
- }
-
- /**
- * Ensure that before the event's listeners are invoked this UIData
- * component's "current row" is set to the row associated with the event.
- * <p>
- * See queueEvent for more details.
- */
- @Override
- public void broadcast(FacesEvent event) throws AbortProcessingException
- {
- if (event instanceof FacesEventWrapper)
- {
- FacesEvent originalEvent = ((FacesEventWrapper) event)
- .getWrappedFacesEvent();
- int eventRowIndex = ((FacesEventWrapper) event).getRowIndex();
- int currentRowIndex = getRowIndex();
- setRowIndex(eventRowIndex);
- try
- {
- originalEvent.getComponent().broadcast(originalEvent);
- }
- finally
- {
- setRowIndex(currentRowIndex);
- }
- }
- else
- {
- super.broadcast(event);
- }
- }
-
- /**
- * Perform necessary actions when rendering of this component starts,
- * before delegating to the inherited implementation which calls the
- * associated renderer's encodeBegin method.
- */
- @Override
- public void encodeBegin(FacesContext context) throws IOException
- {
- _initialDescendantComponentState = null;
- if (_isValidChilds && !hasErrorMessages(context))
- {
- // Clear the data model so that when rendering code calls
- // getDataModel a fresh model is fetched from the backing
- // bean via the value-binding.
- _dataModelMap.clear();
-
- // When the data model is cleared it is also necessary to
- // clear the saved row state, as there is an implicit 1:1
- // relation between objects in the _rowStates and the
- // corresponding DataModel element.
- _rowStates.clear();
- }
- super.encodeBegin(context);
- }
-
- private boolean hasErrorMessages(FacesContext context)
- {
- for(Iterator iter = context.getMessages(); iter.hasNext();)
- {
- FacesMessage message = (FacesMessage) iter.next();
- if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
- {
- return true;
- }
- }
- return false;
- }
-
- /**
- * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
- */
- @Override
- public void encodeEnd(FacesContext context) throws IOException
- {
- setRowIndex(-1);
- super.encodeEnd(context);
- }
-
- @Override
- public void processDecodes(FacesContext context)
- {
- if (context == null)
- throw new NullPointerException("context");
- if (!isRendered())
- return;
- setRowIndex(-1);
- processFacets(context, PROCESS_DECODES);
- processColumnFacets(context, PROCESS_DECODES);
- processColumnChildren(context, PROCESS_DECODES);
- setRowIndex(-1);
- try
- {
- decode(context);
- }
- catch (RuntimeException e)
- {
- context.renderResponse();
- throw e;
- }
- }
-
- @Override
- public void processValidators(FacesContext context)
- {
- if (context == null)
- throw new NullPointerException("context");
- if (!isRendered())
- return;
- setRowIndex(-1);
- processFacets(context, PROCESS_VALIDATORS);
- processColumnFacets(context, PROCESS_VALIDATORS);
- processColumnChildren(context, PROCESS_VALIDATORS);
- setRowIndex(-1);
-
- // check if an validation error forces the render response for our data
- if (context.getRenderResponse())
- {
- _isValidChilds = false;
- }
- }
-
- @Override
- public void processUpdates(FacesContext context)
- {
- if (context == null)
- throw new NullPointerException("context");
- if (!isRendered())
- return;
- setRowIndex(-1);
- processFacets(context, PROCESS_UPDATES);
- processColumnFacets(context, PROCESS_UPDATES);
- processColumnChildren(context, PROCESS_UPDATES);
- setRowIndex(-1);
-
- if (context.getRenderResponse())
- {
- _isValidChilds = false;
- }
- }
-
- private void processFacets(FacesContext context, int processAction)
- {
- for (Iterator it = getFacets().values().iterator(); it.hasNext();)
- {
- UIComponent facet = (UIComponent) it.next();
- process(context, facet, processAction);
- }
- }
-
- /**
- * Invoke the specified phase on all facets of all UIColumn children
- * of this component. Note that no methods are called on the UIColumn
- * child objects themselves.
- *
- * @param context is the current faces context.
- * @param processAction specifies a JSF phase: decode, validate or update.
- */
- private void processColumnFacets(FacesContext context, int processAction)
- {
- for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
- {
- UIComponent child = (UIComponent) childIter.next();
- if (child instanceof UIColumn)
- {
- if (!child.isRendered())
- {
- //Column is not visible
- continue;
- }
- for (Iterator facetsIter = child.getFacets().values()
- .iterator(); facetsIter.hasNext();)
- {
- UIComponent facet = (UIComponent) facetsIter.next();
- process(context, facet, processAction);
- }
- }
- }
- }
-
- /**
- * Invoke the specified phase on all non-facet children of all UIColumn
- * children of this component. Note that no methods are called on the
- * UIColumn child objects themselves.
- *
- * @param context is the current faces context.
- * @param processAction specifies a JSF phase: decode, validate or update.
- */
- private void processColumnChildren(FacesContext context, int processAction)
- {
- int first = _first;
- int rows = _rows;
- int last;
- if (rows == 0)
- {
- last = getRowCount();
- }
- else
- {
- last = first + rows;
- }
- for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
- {
- setRowIndex(rowIndex);
-
- //scrolled past the last row
- if (!isRowAvailable())
- break;
-
- for (Iterator it = getChildren().iterator(); it.hasNext();)
- {
- UIComponent child = (UIComponent) it.next();
- if (child instanceof UIColumn)
- {
- if (!child.isRendered())
- {
- //Column is not visible
- continue;
- }
- for (Iterator columnChildIter = child.getChildren()
- .iterator(); columnChildIter.hasNext();)
- {
- UIComponent columnChild = (UIComponent) columnChildIter
- .next();
- process(context, columnChild, processAction);
- }
- }
- }
- }
- }
-
- private void process(FacesContext context, UIComponent component,
- int processAction)
- {
- switch (processAction)
- {
- case PROCESS_DECODES:
- component.processDecodes(context);
- break;
- case PROCESS_VALIDATORS:
- component.processValidators(context);
- break;
- case PROCESS_UPDATES:
- component.processUpdates(context);
- break;
- }
- }
-
- /**
- * Return the datamodel for this table, potentially fetching the data from
- * a backing bean via a value-binding if this is the first time this method
- * has been called.
- * <p>
- * This is complicated by the fact that this table may be nested within
- * another table. In this case a different datamodel should be fetched
- * for each row. When nested within a parent table, the parent reference
- * won't change but parent.getClientId() will, as the suffix changes
- * depending upon the current row index. A map object on this component
- * is therefore used to cache the datamodel for each row of the table.
- * In the normal case where this table is not nested inside a component
- * that changes its id (like a table does) then this map only ever has
- * one entry.
- */
- protected DataModel getDataModel()
- {
- DataModel dataModel;
- String clientID = "";
-
- UIComponent parent = getParent();
- if (parent != null) {
- clientID = parent.getClientId(getFacesContext());
- }
- dataModel = (DataModel) _dataModelMap.get(clientID);
- if (dataModel == null)
- {
- dataModel = createDataModel();
- _dataModelMap.put(clientID, dataModel);
- }
- return dataModel;
- }
-
- protected void setDataModel(DataModel dataModel)
- {
- throw new UnsupportedOperationException(
- "this method is here only to maintain binary compatibility w/ the RI");
- }
-
- /**
- * Evaluate this object's value property and convert the result into a
- * DataModel. Normally this object's value property will be a value-binding
- * which will cause the value to be fetched from some backing bean.
- * <p>
- * The result of fetching the value may be a DataModel object, in which
- * case that object is returned directly. If the value is of type
- * List, Array, ResultSet, Result, other object or null then an appropriate
- * wrapper is created and returned.
- * <p>
- * Null is never returned by this method.
- */
- private DataModel createDataModel()
- {
- Object value = _value;
- if (value == null)
- {
- return EMPTY_DATA_MODEL;
- }
- else if (value instanceof DataModel)
- {
- return (DataModel) value;
- }
- else if (value instanceof List)
- {
- return new ListDataModel((List) value);
- }
- else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
- {
- return new ArrayDataModel((Object[]) value);
- }
- else if (value instanceof ResultSet)
- {
- return new ResultSetDataModel((ResultSet) value);
- }
- else if (value instanceof Result)
- {
- return new ResultDataModel((Result) value);
- }
- else
- {
- return new ScalarDataModel(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;
- }
- }
-
- /**///setValue
- public void setValue(Object value)
- {
- _value = value;
- _dataModelMap.clear();
- _rowStates.clear();
- _isValidChilds = true;
- }
-
- /**
- * Set the maximum number of rows displayed in the table.
- */
- /**///setRows
- public void setRows(int rows)
- {
- if (rows < 0){
- throw new IllegalArgumentException("rows: " + rows);
- }
- _rows = rows;
- }
-
- /**
- * Set the index of the first row to be displayed, where 0 is the first row.
- */
- /**///setFirst
- public void setFirst(int first)
- {
- if (first < 0) {
- throw new IllegalArgumentException("Illegal value for first row: " + first);
- }
- _first = first;
- }
-
- 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);
- }
- }
+/*
+* Copyright 2004-2006 The Apache Software Foundation.
+*
+* Licensed 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 javax.faces.context.FacesContext;
+import javax.faces.FacesException;
+import javax.faces.application.FacesMessage;
+import javax.faces.event.FacesEvent;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.PhaseId;
+import javax.faces.event.FacesListener;
+import javax.faces.el.ValueBinding;
+import javax.faces.model.DataModel;
+import javax.faces.model.ListDataModel;
+import javax.faces.model.ArrayDataModel;
+import javax.faces.model.ResultSetDataModel;
+import javax.faces.model.ResultDataModel;
+import javax.faces.model.ScalarDataModel;
+import javax.el.ValueExpression;
+import javax.servlet.jsp.jstl.sql.Result;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+import java.sql.ResultSet;
+
+/**
+ * Represents a component which has multiple "rows" of data.
+ * <p>
+ * The children of this component are expected to be UIColumn components.
+ * <p>
+ * 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:
+ * <ul>
+ * <li>submittedValue
+ * <li>value (where no EL binding is used)
+ * <li>valid
+ * </ul>
+ * <p>
+ * 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.
+ * <h1>
+ * Implementation Notes
+ * </h1>
+ * <p>
+ * 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".
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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 <i>not</i>
+ * fetched, and the saved state of the child components is <i>not</i>
+ * discarded.
+ * </p>
+ * see Javadoc of <a href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF Specification</a> for more.
+ *
+ * @author Manfred Geiler (latest modification by $Author$)
+ * @version $Revision$ $Date$
+ */
+abstract public class UIDataTemplate extends UIComponentBase implements NamingContainer
+{
+ 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 String _var = null;
+ /**/private int _rows;
+ /**/private int _first;
+ /**/private Object _value;
+
+ private int _rowIndex = -1;
+
+ // 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.
+ * <p>
+ * 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;
+
+ @Override
+ public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
+ // not supported yet
+ return false;
+ }
+
+ public void setFooter(UIComponent footer)
+ {
+ getFacets().put(FOOTER_FACET_NAME, footer);
+ }
+
+ public UIComponent getFooter()
+ {
+ return (UIComponent) getFacets().get(FOOTER_FACET_NAME);
+ }
+
+ public void setHeader(UIComponent header)
+ {
+ getFacets().put(HEADER_FACET_NAME, header);
+ }
+
+ 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.
+ * <p>
+ * Param rowIndex can be -1, meaning "no row".
+ * <p>
+ * @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(
+ getChildren().iterator(), false);
+ }
+ }
+ else
+ {
+ // 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.
+ _rowStates.put(getClientId(facesContext),
+ saveDescendantComponentStates(getChildren().iterator(),
+ false));
+ }
+
+ _rowIndex = rowIndex;
+
+ DataModel dataModel = getDataModel();
+ dataModel.setRowIndex(rowIndex);
+
+ String var = _var;
+ if (rowIndex == -1)
+ {
+ if (var != null)
+ {
+ facesContext.getExternalContext().getRequestMap().remove(var);
+ }
+ }
+ else
+ {
+ if (var != null)
+ {
+ if (isRowAvailable())
+ {
+ Object rowData = dataModel.getRowData();
+ facesContext.getExternalContext().getRequestMap().put(var,
+ rowData);
+ }
+ else
+ {
+ facesContext.getExternalContext().getRequestMap().remove(
+ var);
+ }
+ }
+ }
+
+ if (_rowIndex == -1)
+ {
+ // reset components to initial state
+ restoreDescendantComponentStates(getChildren().iterator(),
+ _initialDescendantComponentState, false);
+ }
+ else
+ {
+ Object rowState = _rowStates.get(getClientId(facesContext));
+ if (rowState == null)
+ {
+ // We haven't been positioned on this row before, so just
+ // configure the child components of this component with
+ // the standard "initial" state
+ restoreDescendantComponentStates(getChildren().iterator(),
+ _initialDescendantComponentState, false);
+ }
+ else
+ {
+ // We have been positioned on this row before, so configure
+ // the child components of this component with the (partial)
+ // state that was previously saved. Fields not in the
+ // partial saved state are left with their original values.
+ restoreDescendantComponentStates(getChildren().iterator(),
+ rowState, false);
+ }
+ }
+ }
+
+ /**
+ * Overwrite the state of the child components of this component
+ * with data previously saved by method saveDescendantComponentStates.
+ * <p>
+ * The saved state info only covers those fields that are expected to
+ * vary between rows of a table. Other fields are not modified.
+ */
+ private void restoreDescendantComponentStates(Iterator childIterator,
+ Object state, boolean restoreChildFacets)
+ {
+ Iterator descendantStateIterator = null;
+ while (childIterator.hasNext())
+ {
+ if (descendantStateIterator == null && state != null)
+ {
+ descendantStateIterator = ((Collection) state).iterator();
+ }
+ UIComponent component = (UIComponent) 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 = (Object[]) descendantStateIterator.next();
+ childState = object[0];
+ descendantState = object[1];
+ }
+ if (component instanceof EditableValueHolder)
+ {
+ ((EditableValueHolderState) childState)
+ .restoreState((EditableValueHolder) component);
+ }
+ Iterator childsIterator;
+ if (restoreChildFacets)
+ {
+ childsIterator = component.getFacetsAndChildren();
+ }
+ else
+ {
+ childsIterator = component.getChildren().iterator();
+ }
+ restoreDescendantComponentStates(childsIterator, descendantState,
+ true);
+ }
+ }
+ }
+
+ /**
+ * Walk the tree of child components of this UIData, saving the parts of
+ * their state that can vary between rows.
+ * <p>
+ * This is very similar to the process that occurs for normal components
+ * when the view is serialized. Transient components are skipped (no
+ * state is saved for them).
+ * <p>
+ * If there are no children then null is returned. If there are one or
+ * more children, and all children are transient then an empty collection
+ * is returned; this will happen whenever a table contains only read-only
+ * components.
+ * <p>
+ * Otherwise a collection is returned which contains an object for every
+ * non-transient child component; that object may itself contain a collection
+ * of the state of that child's child components.
+ */
+ private Object saveDescendantComponentStates(Iterator childIterator,
+ boolean saveChildFacets)
+ {
+ Collection childStates = null;
+ while (childIterator.hasNext())
+ {
+ if (childStates == null)
+ {
+ childStates = new ArrayList();
+ }
+ UIComponent child = (UIComponent) 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 = saveDescendantComponentStates(
+ childsIterator, true);
+ Object state = null;
+ if (child instanceof EditableValueHolder)
+ {
+ state = new EditableValueHolderState(
+ (EditableValueHolder) child);
+ }
+ childStates.add(new Object[] { state, descendantState });
+ }
+ }
+ return childStates;
+ }
+
+ @Override
+ public void setValueExpression(String name, ValueExpression binding) {
+ if (name == null)
+ {
+ throw new NullPointerException("name");
+ }
+ else if (name.equals("value"))
+ {
+ _dataModelMap.clear();
+ }
+ else if (name.equals("var") || name.equals("rowIndex"))
+ {
+ throw new IllegalArgumentException("name " + name);
+ }
+ super.setValueExpression(name, binding);
+ }
+
+ @Override
+ public String getClientId(FacesContext context)
+ {
+ String clientId = super.getClientId(context);
+ int rowIndex = getRowIndex();
+ if (rowIndex == -1)
+ {
+ return clientId;
+ }
+ return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
+ }
+
+ /**
+ * Modify events queued for any child components so that the
+ * UIData state will be correctly configured before the event's
+ * listeners are executed.
+ * <p>
+ * Child components or their renderers may register events against
+ * those child components. When the listener for that event is
+ * eventually invoked, it may expect the uidata's rowData and
+ * rowIndex to be referring to the same object that caused the
+ * event to fire.
+ * <p>
+ * The original queueEvent call against the child component has been
+ * forwarded up the chain of ancestors in the standard way, making
+ * it possible here to wrap the event in a new event whose source
+ * is <i>this</i> component, not the original one. When the event
+ * finally is executed, this component's broadcast method is invoked,
+ * which ensures that the UIData is set to be at the correct row
+ * before executing the original event.
+ */
+ @Override
+ public void queueEvent(FacesEvent event)
+ {
+ super.queueEvent(new FacesEventWrapper(event, getRowIndex(), this));
+ }
+
+ /**
+ * Ensure that before the event's listeners are invoked this UIData
+ * component's "current row" is set to the row associated with the event.
+ * <p>
+ * See queueEvent for more details.
+ */
+ @Override
+ public void broadcast(FacesEvent event) throws AbortProcessingException
+ {
+ if (event instanceof FacesEventWrapper)
+ {
+ FacesEvent originalEvent = ((FacesEventWrapper) event)
+ .getWrappedFacesEvent();
+ int eventRowIndex = ((FacesEventWrapper) event).getRowIndex();
+ int currentRowIndex = getRowIndex();
+ setRowIndex(eventRowIndex);
+ try
+ {
+ originalEvent.getComponent().broadcast(originalEvent);
+ }
+ finally
+ {
+ setRowIndex(currentRowIndex);
+ }
+ }
+ else
+ {
+ super.broadcast(event);
+ }
+ }
+
+ /**
+ * Perform necessary actions when rendering of this component starts,
+ * before delegating to the inherited implementation which calls the
+ * associated renderer's encodeBegin method.
+ */
+ @Override
+ public void encodeBegin(FacesContext context) throws IOException
+ {
+ _initialDescendantComponentState = null;
+ if (_isValidChilds && !hasErrorMessages(context))
+ {
+ // Clear the data model so that when rendering code calls
+ // getDataModel a fresh model is fetched from the backing
+ // bean via the value-binding.
+ _dataModelMap.clear();
+
+ // When the data model is cleared it is also necessary to
+ // clear the saved row state, as there is an implicit 1:1
+ // relation between objects in the _rowStates and the
+ // corresponding DataModel element.
+ _rowStates.clear();
+ }
+ super.encodeBegin(context);
+ }
+
+ private boolean hasErrorMessages(FacesContext context)
+ {
+ for(Iterator iter = context.getMessages(); iter.hasNext();)
+ {
+ FacesMessage message = (FacesMessage) iter.next();
+ if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
+ */
+ @Override
+ public void encodeEnd(FacesContext context) throws IOException
+ {
+ setRowIndex(-1);
+ super.encodeEnd(context);
+ }
+
+ @Override
+ public void processDecodes(FacesContext context)
+ {
+ if (context == null)
+ throw new NullPointerException("context");
+ if (!isRendered())
+ return;
+ setRowIndex(-1);
+ processFacets(context, PROCESS_DECODES);
+ processColumnFacets(context, PROCESS_DECODES);
+ processColumnChildren(context, PROCESS_DECODES);
+ setRowIndex(-1);
+ try
+ {
+ decode(context);
+ }
+ catch (RuntimeException e)
+ {
+ context.renderResponse();
+ throw e;
+ }
+ }
+
+ @Override
+ public void processValidators(FacesContext context)
+ {
+ if (context == null)
+ throw new NullPointerException("context");
+ if (!isRendered())
+ return;
+ setRowIndex(-1);
+ processFacets(context, PROCESS_VALIDATORS);
+ processColumnFacets(context, PROCESS_VALIDATORS);
+ processColumnChildren(context, PROCESS_VALIDATORS);
+ setRowIndex(-1);
+
+ // check if an validation error forces the render response for our data
+ if (context.getRenderResponse())
+ {
+ _isValidChilds = false;
+ }
+ }
+
+ @Override
+ public void processUpdates(FacesContext context)
+ {
+ if (context == null)
+ throw new NullPointerException("context");
+ if (!isRendered())
+ return;
+ setRowIndex(-1);
+ processFacets(context, PROCESS_UPDATES);
+ processColumnFacets(context, PROCESS_UPDATES);
+ processColumnChildren(context, PROCESS_UPDATES);
+ setRowIndex(-1);
+
+ if (context.getRenderResponse())
+ {
+ _isValidChilds = false;
+ }
+ }
+
+ private void processFacets(FacesContext context, int processAction)
+ {
+ for (Iterator it = getFacets().values().iterator(); it.hasNext();)
+ {
+ UIComponent facet = (UIComponent) it.next();
+ process(context, facet, processAction);
+ }
+ }
+
+ /**
+ * Invoke the specified phase on all facets of all UIColumn children
+ * of this component. Note that no methods are called on the UIColumn
+ * child objects themselves.
+ *
+ * @param context is the current faces context.
+ * @param processAction specifies a JSF phase: decode, validate or update.
+ */
+ private void processColumnFacets(FacesContext context, int processAction)
+ {
+ for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
+ {
+ UIComponent child = (UIComponent) childIter.next();
+ if (child instanceof UIColumn)
+ {
+ if (!child.isRendered())
+ {
+ //Column is not visible
+ continue;
+ }
+ for (Iterator facetsIter = child.getFacets().values()
+ .iterator(); facetsIter.hasNext();)
+ {
+ UIComponent facet = (UIComponent) facetsIter.next();
+ process(context, facet, processAction);
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoke the specified phase on all non-facet children of all UIColumn
+ * children of this component. Note that no methods are called on the
+ * UIColumn child objects themselves.
+ *
+ * @param context is the current faces context.
+ * @param processAction specifies a JSF phase: decode, validate or update.
+ */
+ private void processColumnChildren(FacesContext context, int processAction)
+ {
+ int first = _first;
+ int rows = _rows;
+ int last;
+ if (rows == 0)
+ {
+ last = getRowCount();
+ }
+ else
+ {
+ last = first + rows;
+ }
+ for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
+ {
+ setRowIndex(rowIndex);
+
+ //scrolled past the last row
+ if (!isRowAvailable())
+ break;
+
+ for (Iterator it = getChildren().iterator(); it.hasNext();)
+ {
+ UIComponent child = (UIComponent) it.next();
+ if (child instanceof UIColumn)
+ {
+ if (!child.isRendered())
+ {
+ //Column is not visible
+ continue;
+ }
+ for (Iterator columnChildIter = child.getChildren()
+ .iterator(); columnChildIter.hasNext();)
+ {
+ UIComponent columnChild = (UIComponent) columnChildIter
+ .next();
+ process(context, columnChild, processAction);
+ }
+ }
+ }
+ }
+ }
+
+ private void process(FacesContext context, UIComponent component,
+ int processAction)
+ {
+ switch (processAction)
+ {
+ case PROCESS_DECODES:
+ component.processDecodes(context);
+ break;
+ case PROCESS_VALIDATORS:
+ component.processValidators(context);
+ break;
+ case PROCESS_UPDATES:
+ component.processUpdates(context);
+ break;
+ }
+ }
+
+ /**
+ * Return the datamodel for this table, potentially fetching the data from
+ * a backing bean via a value-binding if this is the first time this method
+ * has been called.
+ * <p>
+ * This is complicated by the fact that this table may be nested within
+ * another table. In this case a different datamodel should be fetched
+ * for each row. When nested within a parent table, the parent reference
+ * won't change but parent.getClientId() will, as the suffix changes
+ * depending upon the current row index. A map object on this component
+ * is therefore used to cache the datamodel for each row of the table.
+ * In the normal case where this table is not nested inside a component
+ * that changes its id (like a table does) then this map only ever has
+ * one entry.
+ */
+ protected DataModel getDataModel()
+ {
+ DataModel dataModel;
+ String clientID = "";
+
+ UIComponent parent = getParent();
+ if (parent != null) {
+ clientID = parent.getClientId(getFacesContext());
+ }
+ dataModel = (DataModel) _dataModelMap.get(clientID);
+ if (dataModel == null)
+ {
+ dataModel = createDataModel();
+ _dataModelMap.put(clientID, dataModel);
+ }
+ return dataModel;
+ }
+
+ protected void setDataModel(DataModel dataModel)
+ {
+ throw new UnsupportedOperationException(
+ "this method is here only to maintain binary compatibility w/ the RI");
+ }
+
+ /**
+ * Evaluate this object's value property and convert the result into a
+ * DataModel. Normally this object's value property will be a value-binding
+ * which will cause the value to be fetched from some backing bean.
+ * <p>
+ * The result of fetching the value may be a DataModel object, in which
+ * case that object is returned directly. If the value is of type
+ * List, Array, ResultSet, Result, other object or null then an appropriate
+ * wrapper is created and returned.
+ * <p>
+ * Null is never returned by this method.
+ */
+ private DataModel createDataModel()
+ {
+ Object value = _value;
+ if (value == null)
+ {
+ return EMPTY_DATA_MODEL;
+ }
+ else if (value instanceof DataModel)
+ {
+ return (DataModel) value;
+ }
+ else if (value instanceof List)
+ {
+ return new ListDataModel((List) value);
+ }
+ else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
+ {
+ return new ArrayDataModel((Object[]) value);
+ }
+ else if (value instanceof ResultSet)
+ {
+ return new ResultSetDataModel((ResultSet) value);
+ }
+ else if (value instanceof Result)
+ {
+ return new ResultDataModel((Result) value);
+ }
+ else
+ {
+ return new ScalarDataModel(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;
+ }
+ }
+
+ /**///setValue
+ public void setValue(Object value)
+ {
+ _value = value;
+ _dataModelMap.clear();
+ _rowStates.clear();
+ _isValidChilds = true;
+ }
+
+ /**
+ * Set the maximum number of rows displayed in the table.
+ */
+ /**///setRows
+ public void setRows(int rows)
+ {
+ if (rows < 0){
+ throw new IllegalArgumentException("rows: " + rows);
+ }
+ _rows = rows;
+ }
+
+ /**
+ * Set the index of the first row to be displayed, where 0 is the first row.
+ */
+ /**///setFirst
+ public void setFirst(int first)
+ {
+ if (first < 0) {
+ throw new IllegalArgumentException("Illegal value for first row: " + first);
+ }
+ _first = first;
+ }
+
+ 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);
+ }
+ }
}