org.apache.myfaces.custom.crosstable.UIColumns Maven / Gradle / Ivy
Show all versions of tomahawk Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.custom.crosstable;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.model.ArrayDataModel;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;
import javax.faces.model.ResultDataModel;
import javax.faces.model.ResultSetDataModel;
import javax.faces.model.ScalarDataModel;
import javax.servlet.jsp.jstl.sql.Result;
/**
* An object which can be a child of a t:dataTable, and inserts a dynamic
* set of columns into the parent table. The set of columns inserted are
* defined by a collection returned from a value-binding.
*
* Class org.apache.myfaces.component.html.ext.HtmlDataTable (aka t:dataTable)
* has special-case code to detect a child of this type, and invoke the
* necessary methods on this class. This class does not work with other
* UIData implementations. There is no renderer for this component; the
* HtmlTableRenderer associated with ext.HtmlDataTable is UIColumns-aware,
* and implements the necessary logic itself.
*
* This class is actually a UIData itself, which is effectively "merged"
* with the parent HtmlDataTable. It can't be used as a stand-alone table,
* however, because it uses the DataModel returned by the "value" value-binding
* to define the columns, and depends on the parent table's "value"
* value-binding to define the row DataModel.
*
* The "value" attribute of this class must be a value-binding which
* returns a DataModel of objects (or a List, Array or ResultSet which
* automatically gets wrapped in the appropriate DataModel).
*
* In a normal table, each UIColumn child has facets and child components
* which define that column. In this component, the same child components
* apply to each column (ie are repeated dataModel.size times). However
* as the columns are rendered, the current DataModel object (ie the object
* defining the current column) is stored into a variable whose name
* is defined by attribute "var". This allows the child components of
* this component to refer to attributes on those objects to set things
* like the current column's name. When the objects must be rendered as
* different components (eg h:outputText or h:outputDate), multiple
* child components can be used with rendered attributes selecting the
* appropriate one depending upon the current column object's data.
*
* @author Mathias Broekelmann (latest modification by $Author: grantsmith $)
* @version $Revision: 472630 $ $Date: 2006-11-08 21:40:03 +0100 (Mi, 08 Nov 2006) $
*/
public class UIColumns extends UIData {
public static final String COMPONENT_TYPE = "org.apache.myfaces.Columns";
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 boolean _isValidChilds = true;
// holds the the state for each cell of the child components of this UIColumns
private Map _cellStates = new HashMap();
private Object _initialDescendantComponentState = null;
private int _colIndex = -1;
private UIData _parentUIData;
private Map _dataModelMap = new HashMap();
public UIColumns() {
super();
}
/**
* Return true if there are any more columns to process.
*/
public boolean isRowAvailable() {
return getDataModel().isRowAvailable();
}
/**
* Get the number of dynamic columns represented by this instance.
*/
public int getRowCount() {
return getDataModel().getRowCount();
}
/** Get the object representing the current column. */
public Object getRowData() {
return getDataModel().getRowData();
}
/** Get the currently selected column index. */
public int getRowIndex() {
return _colIndex;
}
/** Set the currently selected column. */
public void setRowIndex(int colIndex) {
if (colIndex < -1) {
throw new IllegalArgumentException("colIndex is less than -1");
}
if (_colIndex == colIndex) {
return;
}
FacesContext facesContext = getFacesContext();
if (_colIndex == -1) {
if (_initialDescendantComponentState == null) {
_initialDescendantComponentState = saveDescendantComponentStates(getFacetsAndChildren());
}
} else {
_cellStates.put(getClientId(facesContext), saveDescendantComponentStates(getFacetsAndChildren()));
}
_colIndex = colIndex;
DataModel dataModel = getDataModel();
dataModel.setRowIndex(colIndex);
String var = getVar();
if (colIndex == -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 (_colIndex == -1) {
restoreDescendantComponentStates(getFacetsAndChildren(), _initialDescendantComponentState);
} else {
Object rowState = _cellStates.get(getClientId(facesContext));
if (rowState == null) {
restoreDescendantComponentStates(getFacetsAndChildren(), _initialDescendantComponentState);
} else {
restoreDescendantComponentStates(getFacetsAndChildren(), rowState);
}
}
}
protected void restoreDescendantComponentStates(Iterator childIterator, Object state) {
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);
}
restoreDescendantComponentStates(component.getFacetsAndChildren(), descendantState);
}
}
}
protected Object saveDescendantComponentStates(Iterator childIterator) {
Collection childStates = null;
while (childIterator.hasNext()) {
if (childStates == null) {
childStates = new ArrayList();
}
UIComponent child = (UIComponent) childIterator.next();
if (!child.isTransient()) {
Object descendantState = saveDescendantComponentStates(child.getFacetsAndChildren());
Object state = null;
if (child instanceof EditableValueHolder) {
state = new EditableValueHolderState((EditableValueHolder) child);
}
childStates.add(new Object[] { state, descendantState });
}
}
return childStates;
}
public void setValue(Object value) {
super.setValue(value);
_dataModelMap.clear();
_cellStates.clear();
_isValidChilds = true;
}
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);
}
/**
* Get a DataModel whose wrapped data contains a collection of
* objects representing columns.
*/
protected DataModel getDataModel() {
String clientID = "";
UIComponent parent = getParentUIData().getParent();
if (parent != null) {
clientID = parent.getClientId(getFacesContext());
}
DataModel dataModel = (DataModel) _dataModelMap.get(clientID);
if (dataModel == null) {
dataModel = createDataModel();
_dataModelMap.put(clientID, dataModel);
}
return dataModel;
}
protected void setDataModel(DataModel dataModel) {
UIComponent parent = getParentUIData().getParent();
String clientID = "";
if (parent != null) {
clientID = parent.getClientId(getFacesContext());
}
_dataModelMap.put(clientID, dataModel);
}
/**
* Creates a new DataModel around the current value.
*/
protected DataModel createDataModel() {
Object value = getValue();
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 (value instanceof Collection) {
return new ListDataModel(new ArrayList((Collection) 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 final DataModel EMPTY_DATA_MODEL = new DataModel() {
public boolean isRowAvailable() {
return false;
}
public int getRowCount() {
return 0;
}
public Object getRowData() {
throw new IllegalArgumentException();
}
public int getRowIndex() {
return -1;
}
public void setRowIndex(int i) {
if (i < -1)
throw new IndexOutOfBoundsException("Index < 0 : " + i);
}
public Object getWrappedData() {
return null;
}
public void setWrappedData(Object obj) {
if (obj == null)
return; //Clearing is allowed
throw new UnsupportedOperationException(this.getClass().getName() + " UnsupportedOperationException");
}
};
/**
* Update the input component (if any) in each dynamic column.
*
* @see javax.faces.component.UIData#processDecodes(javax.faces.context.FacesContext)
*/
public void processDecodes(FacesContext context) {
if (context == null)
throw new NullPointerException("context");
if (!isRendered())
return;
setRowIndex(-1);
processColumnsFacets(context, PROCESS_DECODES);
processRows(context, PROCESS_DECODES);
setRowIndex(-1);
try {
decode(context);
} catch (RuntimeException e) {
context.renderResponse();
throw e;
}
}
private void processColumnsFacets(FacesContext context, int processAction) {
int first = getFirst();
int cols = getRows();
int last;
if (cols == 0) {
last = getRowCount();
} else {
last = first + cols;
}
for (int colIndex = first; colIndex < last; colIndex++) {
setRowIndex(colIndex);
if (isRowAvailable()) {
for (Iterator facetsIter = getFacets().values().iterator(); facetsIter.hasNext();) {
UIComponent facet = (UIComponent) facetsIter.next();
process(context, facet, processAction);
}
}
}
setRowIndex(-1);
}
private void processRows(FacesContext context, int processAction) {
UIData parentUIData = getParentUIData();
int first = parentUIData.getFirst();
int rows = parentUIData.getRows();
int last;
if (rows == 0) {
last = parentUIData.getRowCount();
} else {
last = first + rows;
}
for (int rowIndex = first; rowIndex < last; rowIndex++) {
parentUIData.setRowIndex(rowIndex);
if (parentUIData.isRowAvailable()) {
processColumns(context, processAction);
}
}
}
/**
* Return the UIData this component is nested within.
*
* Actually, this component will only function correctly when nested within
* an org.apache.myfaces.component.html.ext.HtmlDataTable.
*/
private UIData getParentUIData() {
if (_parentUIData == null) {
UIComponent parent = getParent();
if (!(parent instanceof UIData)) {
throw new IllegalStateException("UIColumns component must be a child of a UIData component");
}
_parentUIData = (UIData) parent;
}
return _parentUIData;
}
private void processColumns(FacesContext context, int processAction) {
int first = getFirst();
int cols = getRows();
int last;
if (cols == 0) {
last = getRowCount();
} else {
last = first + cols;
}
for (int colIndex = first; colIndex < last; colIndex++) {
setRowIndex(colIndex);
if (isRowAvailable()) {
for (Iterator columnChildIter = getChildren().iterator(); columnChildIter.hasNext();) {
UIComponent columnChild = (UIComponent) columnChildIter.next();
process(context, columnChild, processAction);
}
}
}
setRowIndex(-1);
}
/**
* @see javax.faces.component.UIData#processValidators(javax.faces.context.FacesContext)
*/
public void processValidators(FacesContext context) {
if (context == null)
throw new NullPointerException("context");
if (!isRendered())
return;
setRowIndex(-1);
processColumnsFacets(context, PROCESS_VALIDATORS);
processRows(context, PROCESS_VALIDATORS);
setRowIndex(-1);
// check if an validation error forces the render response for our data
if (context.getRenderResponse()) {
_isValidChilds = false;
}
}
/**
* @see javax.faces.component.UIData#processUpdates(javax.faces.context.FacesContext)
*/
public void processUpdates(FacesContext context) {
if (context == null)
throw new NullPointerException("context");
if (!isRendered())
return;
setRowIndex(-1);
processColumnsFacets(context, PROCESS_UPDATES);
processRows(context, PROCESS_UPDATES);
setRowIndex(-1);
// check if an validation error forces the render response for our data
if (context.getRenderResponse()) {
_isValidChilds = false;
}
}
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;
}
}
/**
* Called from HtmlDataTable.encodeBegin, ie called once when rendering
* for the entire table starts.
*/
public void encodeTableBegin(FacesContext context) {
setRowIndex(-1);
_initialDescendantComponentState = null;
if (_isValidChilds && !hasErrorMessages(context)) {
//Refresh DataModel for rendering:
_dataModelMap.clear();
_cellStates.clear();
}
}
protected 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;
}
/**
* Called from HtmlDataTable.encodeEnd, ie called once after rendering
* for the entire table has completed.
*/
public void encodeTableEnd(FacesContext context) {
setRowIndex(-1);
}
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);
}
}
/// ------ EUR/SI/BS/JC MODIFICATIONS START HERE ---------
public String getClientId(FacesContext context) {
String clientId2 = super.getClientId(context);
int rowIndex = getRowIndex();
if (rowIndex == -1) {
return clientId2;
}
else {
return clientId2 + "_" + rowIndex;
}
}
/// ------ EUR/SI/BS/JC MODIFICATIONS END HERE ---------
}