/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.ajax4jsf.component;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
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.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.render.Renderer;
import org.ajax4jsf.model.DataComponentState;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.SerializableDataModel;
import org.ajax4jsf.renderkit.AjaxChildrenRenderer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Base class for iterable components, like dataTable, Tomahawk dataList,
* Facelets repeat, tree etc., with support for partial rendering on AJAX
* responces for one or more selected iterations.
*
* @author shura
*
*/
public abstract class UIDataAdaptor extends UIData implements AjaxDataEncoder {
/**
*
*/
public static final String COMPONENT_STATE_ATTRIBUTE = "componentState";
public final static DataModel EMPTY_MODEL = new ListDataModel(
Collections.EMPTY_LIST);
private static final Log _log = LogFactory.getLog(UIDataAdaptor.class);
/**
* Base class for visit data model at phases decode, validation and update
* model
*
* @author shura
*
*/
protected abstract class ComponentVisitor implements DataVisitor {
public void process(FacesContext context, Object rowKey, Object argument)
throws IOException {
setRowKey(context, rowKey);
if (isRowAvailable()) {
Iterator childIterator = dataChildren();
while (childIterator.hasNext()) {
UIComponent component = (UIComponent) childIterator.next();
processComponent(context, component, argument);
}
}
}
public abstract void processComponent(FacesContext context,
UIComponent c, Object argument) throws IOException;
}
/**
* Visitor for process decode on children components.
*/
protected ComponentVisitor decodeVisitor = new ComponentVisitor() {
public void processComponent(FacesContext context, UIComponent c,
Object argument) {
c.processDecodes(context);
}
};
/**
* Visitor for process validation phase
*/
protected ComponentVisitor validateVisitor = new ComponentVisitor() {
public void processComponent(FacesContext context, UIComponent c,
Object argument) {
c.processValidators(context);
}
};
/**
* Visitor for process update model phase.
*/
protected ComponentVisitor updateVisitor = new ComponentVisitor() {
public void processComponent(FacesContext context, UIComponent c,
Object argument) {
c.processUpdates(context);
}
};
/**
* Base client id's of this component, for wich invoked encode... methods.
* Component will save state and serialisable models for this keys only.
*/
private Set _encoded;
/**
* Storage for data model instances with different client id's of this
* component. In case of child for UIData component, this map will keep data
* models for different iterations between phases.
*/
private Map _modelsMap = new HashMap();
/**
* Reference for curent data model
*/
private ExtendedDataModel _currentModel = null;
/**
* States of this component for diferent iterations, same as for models.
*/
private Map _statesMap = new HashMap();
/**
* Reference for current component state.
*/
private DataComponentState _currentState = null;
/**
* Name of EL variable for current component state.
*/
private String _stateVar;
private String _rowKeyVar;
/**
* Key for current value in model.
*/
private Object _rowKey = null;
/**
* Values of row keys, encoded on ajax response rendering.
*/
private Set _ajaxKeys = null;
/**
* Internal set of row keys, encoded on ajax response rendering and cleared after response complete
*/
private Set _ajaxRequestKeys = null;
private Object _ajaxRowKey = null;
private Map _ajaxRowKeysMap = new HashMap();
/**
* Get name of EL variable for component state.
*
* @return the varState
*/
public String getStateVar() {
return _stateVar;
}
/**
* @param varStatus
* the varStatus to set
*/
public void setStateVar(String varStatus) {
this._stateVar = varStatus;
}
/**
* @return the rowKeyVar
*/
public String getRowKeyVar() {
return this._rowKeyVar;
}
/**
* @param rowKeyVar
* the rowKeyVar to set
*/
public void setRowKeyVar(String rowKeyVar) {
this._rowKeyVar = rowKeyVar;
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#getRowCount()
*/
public int getRowCount() {
return getExtendedDataModel().getRowCount();
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#getRowData()
*/
public Object getRowData() {
return getExtendedDataModel().getRowData();
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#isRowAvailable()
*/
public boolean isRowAvailable() {
return this.getExtendedDataModel().isRowAvailable();
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#setRowIndex(int)
*/
public void setRowIndex(int index) {
FacesContext faces = FacesContext.getCurrentInstance();
ExtendedDataModel localModel = getExtendedDataModel();
boolean rowAvailable = isRowAvailable();
// if(key == localModel.getRowIndex()){
// return;
// }
if (rowAvailable) {
// save child state
this.saveChildState(faces);
}
// Set current model row by int, but immediately get value from model.
// for compability, complex models must provide values map between
// integer and key value.
localModel.setRowIndex(index);
rowAvailable = isRowAvailable();
this._rowKey = localModel.getRowKey();
this._clientId = null;
boolean rowSelected = this._rowKey != null && rowAvailable;
setupVariable(faces, localModel, rowSelected);
if (rowAvailable) {
// restore child state
this.restoreChildState(faces);
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#getRowIndex()
*/
public int getRowIndex() {
return getExtendedDataModel().getRowIndex();
}
/**
* Same as for int index, but for complex model key.
*
* @return
*/
public Object getRowKey() {
return this._rowKey;
}
public void setRowKey(Object key) {
setRowKey(FacesContext.getCurrentInstance(), key);
}
/**
* Setup current roy by key. Perform same functionality as
* {@link UIData#setRowIndex(int)}, but for key object - it may be not only
* row number in sequence data, but, for example - path to current node in
* tree.
*
* @param faces -
* current FacesContext
* @param key
* new key value.
*/
public void setRowKey(FacesContext faces, Object key) {
ExtendedDataModel localModel = getExtendedDataModel();
boolean rowAvailable = isRowAvailable();
if (rowAvailable) {
// save child state
this.saveChildState(faces);
}
this._rowKey = key;
this._clientId = null;
localModel.setRowKey(key);
rowAvailable = isRowAvailable();
boolean rowSelected = key != null && rowAvailable;
//XXX check for row availability
setupVariable(faces, localModel, rowSelected);
if (rowAvailable) {
// restore child state
this.restoreChildState(faces);
}
}
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#getAjaxKeys()
*/
public Set getAjaxKeys() {
Set keys = null;
if (this._ajaxKeys != null) {
keys = (this._ajaxKeys);
} else {
ValueBinding vb = getValueBinding("ajaxKeys");
if (vb != null) {
keys = (Set) (vb.getValue(getFacesContext()));
} else if (null != _ajaxRowKey) {
// If none of above exist , use row with submitted AjaxComponent
keys = new HashSet(1);
keys.add(_ajaxRowKey);
}
}
return keys;
}
public Set getAllAjaxKeys() {
Set ajaxKeys = getAjaxKeys();
Set allAjaxKeys = null;
if (ajaxKeys != null) {
allAjaxKeys = new HashSet();
allAjaxKeys.addAll(ajaxKeys);
}
if (_ajaxRequestKeys != null) {
if (allAjaxKeys == null) {
allAjaxKeys = new HashSet();
}
allAjaxKeys.addAll(_ajaxRequestKeys);
}
return allAjaxKeys;
}
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.ajax.repeat.AjaxDataEncoder#setAjaxKeys(java.util.Set)
*/
public void setAjaxKeys(Set ajaxKeys) {
this._ajaxKeys = ajaxKeys;
}
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.framework.ajax.AjaxChildrenEncoder#encodeAjaxChild(javax.faces.context.FacesContext,
* java.lang.String, java.util.Set, java.util.Set)
*/
public void encodeAjaxChild(FacesContext context, String path,
final Set ids, final Set renderedAreas) throws IOException {
resetDataModel();
Renderer renderer = getRenderer(context);
if (null != renderer && renderer instanceof AjaxChildrenRenderer) {
// If renderer support partial encoding - call them.
if(_log.isDebugEnabled()){
_log.debug("Component "+getClientId(context)+" has delegated Encode children components by AjaxChildrenRenderer for path "+path);
}
AjaxChildrenRenderer childrenRenderer = (AjaxChildrenRenderer) renderer;
childrenRenderer.encodeAjaxChildren(context, this, path, ids,
renderedAreas);
} else {
if(_log.isDebugEnabled()){
_log.debug("Component "+getClientId(context)+" do Encode children components for path "+path);
}
// Use simple ajax children encoding for iterate other keys.
final AjaxChildrenRenderer childrenRenderer = getChildrenRenderer();
final String childrenPath = path + getId()
+ NamingContainer.SEPARATOR_CHAR;
ComponentVisitor ajaxVisitor = new ComponentVisitor() {
public void processComponent(FacesContext context,
UIComponent c, Object argument) throws IOException {
childrenRenderer.encodeAjaxComponent(context, c,
childrenPath, ids, renderedAreas);
}
};
Set ajaxKeys = getAllAjaxKeys();
if (null != ajaxKeys) {
if(_log.isDebugEnabled()){
_log.debug("Component "+getClientId(context)+" Encode children components for a keys "+ajaxKeys);
}
captureOrigValue();
Object savedKey = getRowKey();
setRowKey(context, null);
Iterator fixedChildren = fixedChildren();
while (fixedChildren.hasNext()) {
UIComponent component = (UIComponent) fixedChildren.next();
ajaxVisitor.processComponent(context, component, null);
}
for (Iterator iter = ajaxKeys.iterator(); iter.hasNext();) {
Object key = (Object) iter.next();
ajaxVisitor.process(context, key, null);
}
setRowKey(context,savedKey);
restoreOrigValue(context);
} else {
if(_log.isDebugEnabled()){
_log.debug("Component "+getClientId(context)+" children components for all rows");
}
iterate(context, ajaxVisitor, null);
}
}
}
/**
* Instance of default renderer in ajax responses.
*/
private AjaxChildrenRenderer _childrenRenderer = null;
/**
* getter for simple {@link AjaxChildrenRenderer} instance in case of ajax
* responses. If default renderer not support search of children for encode
* in ajax response, component will use this instance by default.
*
* @return
*/
protected AjaxChildrenRenderer getChildrenRenderer() {
if (_childrenRenderer == null) {
_childrenRenderer = new AjaxChildrenRenderer() {
protected Class getComponentClass() {
return UIDataAdaptor.class;
}
};
}
return _childrenRenderer;
}
/**
* @return Set of values for clientId's of this component, for wich was
* invoked "encode" methods.
*/
protected Set getEncodedIds() {
if (_encoded == null) {
_encoded = new HashSet();
}
return _encoded;
}
/**
* Setup EL variable for different iteration. Value of row data and
* component state will be put into request scope attributes with names
* given by "var" and "varState" bean properties.
*
* Changed: does not check for row availability now
*
* @param faces
* current faces context
* @param localModel
* @param rowSelected
*/
protected void setupVariable(FacesContext faces, DataModel localModel,
boolean rowSelected) {
Map attrs = faces.getExternalContext().getRequestMap();
if (rowSelected/*&& isRowAvailable()*/) {
// Current row data.
setupVariable(getVar(), attrs, localModel.getRowData());
// Component state variable.
setupVariable(getStateVar(), attrs, getComponentState());
// Row key Data variable.
setupVariable(getRowKeyVar(), attrs, getRowKey());
} else {
removeVariable(getVar(), attrs);
removeVariable(getStateVar(), attrs);
removeVariable(getRowKeyVar(), attrs);
}
}
/**
* @param var
* @param attrs
* @param rowData
*/
private void setupVariable(String var, Map attrs, Object rowData) {
if (var != null) {
attrs.put(var, rowData);
}
}
/**
* @param var
* @param attrs
* @param rowData
*/
private void removeVariable(String var, Map attrs) {
if (var != null) {
attrs.remove(var);
}
}
/**
* Reset data model. this method must be called twice per request - before
* decode phase and before component encoding.
*/
protected void resetDataModel() {
this.setExtendedDataModel(null);
}
/**
* Set data model. Model value will be stored in Map with key as current
* clientId for this component, to keep models between phases for same
* iteration in case if this component child for other UIData
*
* @param model
*/
protected void setExtendedDataModel(ExtendedDataModel model) {
this._currentModel = model;
this._modelsMap.put(getBaseClientId(getFacesContext()), model);
}
/**
* Get current data model, or create it by {@link #createDataModel()}
* method. For different iterations in ancestor UIData ( if present ) will
* be returned different models.
*
* @return current data model.
*/
protected ExtendedDataModel getExtendedDataModel() {
if (this._currentModel == null) {
String baseClientId = getBaseClientId(getFacesContext());
ExtendedDataModel model;
model = (ExtendedDataModel) this._modelsMap.get(baseClientId);
if (null == model) {
model = createDataModel();
this._modelsMap.put(baseClientId, model);
}
this._currentModel = model;
}
return this._currentModel;
}
/**
* Hook mathod for create data model in concrete implementations.
*
* @return
*/
protected abstract ExtendedDataModel createDataModel();
/**
* Set current state ( at most cases, visual representation ) of this
* component. Same as for DataModel, component will keep states for
* different iterations.
*
* @param state
*/
public void setComponentState(DataComponentState state) {
this._currentState = state;
this._statesMap.put(getBaseClientId(getFacesContext()),
this._currentState);
}
/**
* @return current state of this component.
*/
public DataComponentState getComponentState() {
DataComponentState state = null;
if (this._currentState == null) {
// Check for binding state to user bean.
ValueBinding valueBinding = getValueBinding(UIDataAdaptor.COMPONENT_STATE_ATTRIBUTE);
FacesContext facesContext = getFacesContext();
if (null != valueBinding) {
state = (DataComponentState) valueBinding
.getValue(facesContext);
if (null == state) {
// Create default state
state = createComponentState();
if (!valueBinding.isReadOnly(facesContext)) {
// Store created state in user bean.
valueBinding.setValue(facesContext, state);
}
}
} else {
// Check for stored state in map for parent iterations
String baseClientId = getBaseClientId(facesContext);
state = (DataComponentState) this._statesMap.get(baseClientId);
if (null == state) {
// Create default component state
state = createComponentState();
this._statesMap.put(baseClientId, state);
}
this._currentState = state;
}
} else {
state = this._currentState;
}
return state;
}
/**
* Hook method for create default state in concrete implementations.
*
* @return
*/
protected abstract DataComponentState createComponentState();
private String _clientId = null;
public String getClientId(FacesContext faces) {
if (null == _clientId) {
StringBuffer id = new StringBuffer(getBaseClientId(faces));
Object rowKey = getRowKey();
if (rowKey != null) {
id.append(NamingContainer.SEPARATOR_CHAR).append(
rowKey.toString());
}
Renderer renderer;
if (null != (renderer = getRenderer(faces))) {
_clientId = renderer.convertClientId(faces, id.toString());
} else {
_clientId = id.toString();
}
}
return _clientId;
}
private String _baseClientId = null;
/**
* Get base clietntId of this component ( withowt iteration part )
*
* @param faces
* @return
*/
public String getBaseClientId(FacesContext faces) {
// Return any previously cached client identifier
if (_baseClientId == null) {
// Search for an ancestor that is a naming container
UIComponent ancestorContainer = this;
StringBuffer parentIds = new StringBuffer();
while (null != (ancestorContainer = ancestorContainer.getParent())) {
if (ancestorContainer instanceof NamingContainer) {
parentIds.append(ancestorContainer.getClientId(faces))
.append(NamingContainer.SEPARATOR_CHAR);
break;
}
}
String id = getId();
if (null != id) {
_baseClientId = parentIds.append(id).toString();
} else {
_baseClientId = parentIds.append(
faces.getViewRoot().createUniqueId()).toString();
}
}
return (_baseClientId);
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIComponentBase#setId(java.lang.String)
*/
public void setId(String id) {
// If component created by restoring tree or JSP, initial Id is null.
boolean haveId = null != super.getId();
String baseClientId;
// baseClientId = haveId ? getBaseClientId(getFacesContext())
// : null;
super.setId(id);
_baseClientId = null;
_clientId = null;
if (haveId) {
// parent UIData ( if present ) will be set same Id at iteration
// -
// we use it for
// switch to different model and state.
// Step one - save old values.
// this._statesMap.put(baseClientId, this._currentState);
// this._modelsMap.put(baseClientId, this._currentModel);
// this._ajaxRowKeysMap.put(baseClientId, this._ajaxRowKey);
// Step two - restore values for a new clientId.
baseClientId = getBaseClientId(getFacesContext());
this._currentState = (DataComponentState) this._statesMap
.get(baseClientId);
this._currentModel = (ExtendedDataModel) this._modelsMap
.get(baseClientId);
if (null != this._currentModel) {
this._rowKey = this._currentModel.getRowKey();
// restoreChildState();
}
// Restore value for row with submitted AjaxComponent.
this._ajaxRowKey = _ajaxRowKeysMap.get(baseClientId);
}
}
private Object origValue;
/**
* Save current state of data variable.
*/
public void captureOrigValue() {
captureOrigValue(FacesContext.getCurrentInstance());
}
/**
* Save current state of data variable.
*
* @param faces
* current faces context
*/
public void captureOrigValue(FacesContext faces) {
String var = getVar();
if (var != null) {
Map attrs = faces.getExternalContext().getRequestMap();
this.origValue = attrs.get(var);
}
}
/**
* Restore value of data variable after processing phase.
*/
public void restoreOrigValue() {
restoreOrigValue(FacesContext.getCurrentInstance());
}
/**
* Restore value of data variable after processing phase.
*
* @param faces
* current faces context
*/
public void restoreOrigValue(FacesContext faces) {
String var = getVar();
if (var != null) {
Map attrs = faces.getExternalContext().getRequestMap();
if (this.origValue != null) {
attrs.put(var, this.origValue);
} else {
attrs.remove(var);
}
}
}
/**
* Saved values of {@link EditableValueHolder} fields per iterations.
*/
private Map childState;
/**
* @param faces
* @return Saved values of {@link EditableValueHolder} fields per
* iterations.
*/
protected Map getChildState(FacesContext faces) {
if (this.childState == null) {
this.childState = new HashMap();
}
String baseClientId = getBaseClientId(faces);
Map currentChildState = (Map) childState.get(baseClientId);
if (null == currentChildState) {
currentChildState = new HashMap();
childState.put(baseClientId, currentChildState);
}
return currentChildState;
}
/**
* Save values of {@link EditableValueHolder} fields before change current
* row.
*
* @param faces
*/
protected void saveChildState(FacesContext faces) {
Iterator itr = dataChildren();
while (itr.hasNext()) {
Map childState = this.getChildState(faces);
this.saveChildState(faces, (UIComponent) itr.next(), childState);
}
}
/**
* Recursive method for Iterate on children for save
* {@link EditableValueHolder} fields states.
*
* @param faces
* @param c
* @param childState
*/
private void saveChildState(FacesContext faces, UIComponent c,
Map childState) {
if (c instanceof EditableValueHolder && !c.isTransient()) {
String clientId = c.getClientId(faces);
SavedState ss = (SavedState) childState.get(clientId);
if (ss == null) {
ss = new SavedState();
childState.put(clientId, ss);
}
ss.populate((EditableValueHolder) c);
}
// continue hack
Iterator itr = c.getChildren().iterator();
while (itr.hasNext()) {
saveChildState(faces, (UIComponent) itr.next(), childState);
}
itr = c.getFacets().values().iterator();
while (itr.hasNext()) {
saveChildState(faces, (UIComponent) itr.next(), childState);
}
}
/**
* Restore values of {@link EditableValueHolder} fields after change current
* row.
*
* @param faces
*/
protected void restoreChildState(FacesContext faces) {
Iterator itr = dataChildren();
while (itr.hasNext()) {
Map childState = this.getChildState(faces);
this.restoreChildState(faces, (UIComponent) itr.next(), childState);
}
}
/**
* Recursive part of
* {@link #restoreChildState(FacesContext, UIComponent, Map)}
*
* @param faces
* @param c
* @param childState
*
*/
private void restoreChildState(FacesContext faces, UIComponent c,
Map childState) {
// reset id
String id = c.getId();
c.setId(id);
// hack
if (c instanceof EditableValueHolder) {
EditableValueHolder evh = (EditableValueHolder) c;
String clientId = c.getClientId(faces);
SavedState ss = (SavedState) childState.get(clientId);
if (ss != null) {
ss.apply(evh);
} else {
NullState.apply(evh);
}
}
// continue hack
Iterator itr = c.getChildren().iterator();
while (itr.hasNext()) {
restoreChildState(faces, (UIComponent) itr.next(), childState);
}
itr = c.getFacets().values().iterator();
while (itr.hasNext()) {
restoreChildState(faces, (UIComponent) itr.next(), childState);
}
}
/**
* Check for validation errors on children components. If true, saved values
* must be keep on render phase
*
* @param context
* @return
*/
private boolean keepSaved(FacesContext context) {
Iterator clientIds = this.getChildState(context).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 false;
}
/**
* Perform iteration on all children components and all data rows with given
* visitor.
*
* @param faces
* @param visitor
*/
protected void iterate(FacesContext faces, ComponentVisitor visitor,
Object argument) {
// stop if not rendered
if (!this.isRendered()) {
return;
}
// reset rowIndex
this.captureOrigValue(faces);
this.setRowKey(faces, null);
try {
Iterator fixedChildren = fixedChildren();
while (fixedChildren.hasNext()) {
UIComponent component = (UIComponent) fixedChildren.next();
visitor.processComponent(faces, component, argument);
}
walk(faces, visitor, argument);
} catch (Exception e) {
throw new FacesException(e);
} finally {
this.setRowKey(faces, null);
this.restoreOrigValue(faces);
}
}
/**
* Walk ( visit ) this component on all data-avare children for each row.
*
* @param faces
* @param visitor
* @throws IOException
*/
public void walk(FacesContext faces, DataVisitor visitor, Object argument)
throws IOException {
getExtendedDataModel().walk(faces, visitor,
getComponentState().getRange(), argument);
}
protected void processDecodes(FacesContext faces, Object argument) {
if (!this.isRendered())
return;
this.iterate(faces, decodeVisitor, argument);
this.decode(faces);
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#processDecodes(javax.faces.context.FacesContext)
*/
public void processDecodes(FacesContext faces) {
processDecodes(faces, null);
}
/**
* Reset per-request fields in component.
*
* @param faces
*
*/
protected void resetComponent(FacesContext faces) {
// resetDataModel();
if (null != this.childState) {
childState.remove(getBaseClientId(faces));
}
this._encoded = null;
}
protected void processUpdates(FacesContext faces, Object argument) {
if (!this.isRendered())
return;
this.iterate(faces, updateVisitor, argument);
ExtendedDataModel dataModel = getExtendedDataModel();
// If no validation errors, update values for serializable model,
// restored from view.
if (dataModel instanceof SerializableDataModel && (!keepSaved(faces))) {
SerializableDataModel serializableModel = (SerializableDataModel) dataModel;
serializableModel.update();
}
}
public void processUpdates(FacesContext faces) {
processUpdates(faces, null);
// resetComponent(faces);
}
protected void processValidators(FacesContext faces, Object argument) {
if (!this.isRendered())
return;
this.iterate(faces, validateVisitor, argument);
}
public void processValidators(FacesContext faces) {
processValidators(faces, null);
}
public void encodeBegin(FacesContext context) throws IOException {
resetDataModel();
if (!keepSaved(context)) {
childState.remove(getBaseClientId(context));
}
// Mark component as used, if parent UIData change own range states not
// accessed at
// encode phase must be unsaved.
getEncodedIds().add(getBaseClientId(context));
// getComponentState().setUsed(true);
super.encodeBegin(context);
}
/**
* This method must create iterator for all non-data avare children of this
* component ( header/footer facets for components and columns in dataTable,
* facets for tree etc.
*
* @return iterator for all components not sensitive for row data.
*/
protected abstract Iterator fixedChildren();
/**
* This method must create iterator for all children components, processed
* "per row" It can be children of UIColumn in dataTable, nodes in tree
*
* @return iterator for all components processed per row.
*/
protected abstract Iterator dataChildren();
private final static SavedState NullState = new SavedState();
// from RI
/**
* This class keep values of {@link EditableValueHolder} row-sensitive
* fields.
*
* @author shura
*
*/
private final static class SavedState implements Serializable {
private Object submittedValue;
private static final long serialVersionUID = 2920252657338389849L;
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);
}
public void populate(EditableValueHolder evh) {
this.value = evh.getLocalValue();
this.valid = evh.isValid();
this.submittedValue = evh.getSubmittedValue();
this.localValueSet = evh.isLocalValueSet();
}
public void apply(EditableValueHolder evh) {
evh.setValue(this.value);
evh.setValid(this.valid);
evh.setSubmittedValue(this.submittedValue);
evh.setLocalValueSet(this.localValueSet);
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.component.UIData#queueEvent(javax.faces.event.FacesEvent)
*/
public void queueEvent(FacesEvent event) {
if (event.getComponent() != this) {
event = new IndexedEvent(this, event, getRowKey());
}
// Send event directly to parent, to avoid wrapping in superclass.
UIComponent parent = getParent();
if (parent == null) {
throw new IllegalStateException(
"No parent component for queue event");
} else {
parent.queueEvent(event);
}
}
public void broadcast(FacesEvent event) throws AbortProcessingException {
if (!(event instanceof IndexedEvent)) {
if (!broadcastLocal(event)) {
super.broadcast(event);
}
return;
}
// Set up the correct context and fire our wrapped event
IndexedEvent revent = (IndexedEvent) event;
Object oldRowKey = getRowKey();
FacesContext faces = FacesContext.getCurrentInstance();
captureOrigValue(faces);
Object eventRowKey = revent.getKey();
setRowKey(faces, eventRowKey);
FacesEvent rowEvent = revent.getTarget();
rowEvent.getComponent().broadcast(rowEvent);
// For Ajax events, keep row value.
if (!(rowEvent.getPhaseId() == PhaseId.RENDER_RESPONSE)) {
this._ajaxRowKey = eventRowKey;
// this._ajaxRowKeysMap.put(getBaseClientId(faces), eventRowKey);
}
setRowKey(faces, oldRowKey);
restoreOrigValue(faces);
// }
return;
}
/**
* Process events targetted for concrete implementation. Hook method called
* from {@link #broadcast(FacesEvent)}
*
* @param event -
* processed event.
* @return true if event processed, false if component must continue
* processing.
*/
protected boolean broadcastLocal(FacesEvent event) {
return false;
}
/**
* Wrapper for event from child component, with value of current row key.
*
* @author shura
*
*/
protected static final class IndexedEvent extends FacesEvent {
private static final long serialVersionUID = -8318895390232552385L;
private final FacesEvent target;
private final Object key;
public IndexedEvent(UIDataAdaptor owner, FacesEvent target, Object key) {
super(owner);
this.target = target;
this.key = key;
}
public PhaseId getPhaseId() {
return (this.target.getPhaseId());
}
public void setPhaseId(PhaseId phaseId) {
this.target.setPhaseId(phaseId);
}
public boolean isAppropriateListener(FacesListener listener) {
return this.target.isAppropriateListener(listener);
}
public void processListener(FacesListener listener) {
UIDataAdaptor owner = (UIDataAdaptor) this.getComponent();
Object prevIndex = owner._rowKey;
try {
owner.setRowKey(this.key);
this.target.processListener(listener);
} finally {
owner.setRowKey(prevIndex);
}
}
public Object getKey() {
return key;
}
public FacesEvent getTarget() {
return target;
}
}
/**
* "memento" pattern class for state of component.
*
* @author shura
*
*/
private static class DataState implements Serializable {
/**
*
*/
private static final long serialVersionUID = 17070532L;
private Object superState;
private Map componentStates = new HashMap();
private Set ajaxKeys;
public String rowKeyVar;
public String stateVar;
private Map childStates;
}
/**
* Serialisable model and component state per iteration of parent UIData.
*
* @author shura
*
*/
private static class PerIdState implements Serializable {
/**
*
*/
private static final long serialVersionUID = 9037454770537726418L;
/**
* Flag setted to true if componentState implements StateHolder
*/
private boolean stateInHolder = false;
/**
* Serializable componentState or
*/
private Object componentState;
private SerializableDataModel model;
}
public void restoreState(FacesContext faces, Object object) {
DataState state = (DataState) object;
super.restoreState(faces, state.superState);
this._ajaxKeys = state.ajaxKeys;
this._statesMap = new HashMap();
this._rowKeyVar = state.rowKeyVar;
this._stateVar = state.stateVar;
this.childState = state.childStates;
// Restore serializable models and component states for all rows of
// parent UIData ( single if this
// component not child of iterable )
for (Iterator iter = state.componentStates.entrySet().iterator(); iter
.hasNext();) {
Map.Entry stateEntry = (Map.Entry) iter.next();
PerIdState idState = (PerIdState) stateEntry.getValue();
DataComponentState compState;
if (idState.stateInHolder) {
// TODO - change RichFaces Tree component, for remove reference
// to component from state.
compState = createComponentState();
((StateHolder) compState).restoreState(faces,
idState.componentState);
} else {
compState = (DataComponentState) idState.componentState;
}
Object key = stateEntry.getKey();
this._statesMap.put(key, compState);
this._modelsMap.put(key, idState.model);
}
}
public Object saveState(FacesContext faces) {
DataState state = new DataState();
state.superState = super.saveState(faces);
state.ajaxKeys = this._ajaxKeys;
state.rowKeyVar = this._rowKeyVar;
state.stateVar = this._stateVar;
state.childStates = this.childState;
Set encodedIds = getEncodedIds();
// Save all states of component and data model for all valies of
// clientId, encoded in this request.
// this._statesMap.put(getBaseClientId(faces), this._currentState);
// this._modelsMap.put(getBaseClientId(faces), this._currentModel);
for (Iterator iter = this._statesMap.entrySet().iterator(); iter
.hasNext();) {
Map.Entry stateEntry = (Map.Entry) iter.next();
DataComponentState dataComponentState = ((DataComponentState) stateEntry
.getValue());
Object stateKey = stateEntry.getKey();
if (encodedIds.isEmpty() || encodedIds.contains(stateKey)) {
PerIdState idState = new PerIdState();
// Save component state , depended if implemented interfaces.
if (null == dataComponentState) {
idState.componentState = null;
} else {
if (dataComponentState instanceof Serializable) {
idState.componentState = dataComponentState;
} else if (dataComponentState instanceof StateHolder) {
idState.componentState = ((StateHolder) dataComponentState)
.saveState(faces);
idState.stateInHolder = true;
}
ExtendedDataModel extendedDataModel = (ExtendedDataModel) this._modelsMap
.get(stateKey);
if (null != extendedDataModel) {
idState.model = extendedDataModel
.getSerializableModel(dataComponentState
.getRange());
}
}
if (null != idState.model || null != idState.componentState) {
state.componentStates.put(stateKey, idState);
}
}
}
return state;
}
public void setParent(UIComponent parent) {
super.setParent(parent);
this._clientId = null;
this._baseClientId = null;
}
/**
* Adds argument key to AJAX internal request keys set
* @param key key to add
*/
public void addRequestKey(Object key) {
if (_ajaxRequestKeys == null) {
_ajaxRequestKeys = new HashSet();
}
_ajaxRequestKeys.add(key);
}
/**
* Removes argument key from AJAX internal request keys set
* @param key key to remove
*/
public void removeRequestKey(Object key) {
if (_ajaxRequestKeys != null && key != null) {
_ajaxRequestKeys.remove(key);
}
}
/**
* Checks whether AJAX internal request keys set contains argument key
* @param key key to check
* @return <code>true</code> if set contains key, <code>false</code> - otherwise
*/
public boolean containsRequestKey(Object key) {
if (_ajaxRequestKeys != null && key != null) {
return _ajaxRequestKeys.contains(key);
}
return false;
}
/**
* Clears AJAX internal request keys set
*/
public void clearRequestKeysSet() {
_ajaxRequestKeys = null;
}
/**
* Getter for value
*/
public Object getValue() {
return super.getValue();
}
/**
* Setter for value resetting local model
*/
public void setValue(Object value) {
setExtendedDataModel(null);
super.setValue(value);
}
}