package net.techreadiness.ui.tags.datagrid;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.techreadiness.service.common.DataGrid;
import net.techreadiness.service.common.DataGridColumn;
import net.techreadiness.service.common.DataGridItemProvider;
import net.techreadiness.service.common.SelectableItemProvider;
import net.techreadiness.service.common.ViewDef;
import net.techreadiness.service.common.ViewField;
import net.techreadiness.ui.tags.ParentTag;
import net.techreadiness.ui.tags.ToolbarTag;
import net.techreadiness.ui.tags.dataview.DataViewControlTag;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
public class DataGridTag<T> extends ParentTag implements DataGrid<T>, Serializable {
private final Logger log = LoggerFactory.getLogger(DataGridTag.class);
private static final String JSP_LOCATION = "/dataGrid/dataGrid.jsp";
private static final long serialVersionUID = 1L;
private String action;
private final List<DataGridColumnTag> allColumns;
private final List<Entry<String, String>> appliedFilters;
private List<DataGridColumnGroupTag> columnGroups;
private boolean columnSelectable;
private Collection<T> currentPage;
private List<DataViewControlTag> dataViewControls;
private final Map<String, DataGridColumnTag> displayedColumns;
private boolean editAlertDisabled;
private String errorMessage;
private boolean executed;
private String fieldName;
private List<DataGridColumnTag> hiddenColumns;
private Set<String> hiddenFieldsInError;
private boolean illegalState;
private boolean inlineEditable;
private String inlineSaveAction;
private DataGridItemProvider<T> itemProvider;
private boolean multiEditable;
private String namespace;
private String rowValue;
private String saveAction;
private boolean searchable;
private boolean selectable;
private boolean selectAllRows;
private String selectedDisabledTest;
private boolean showRequired;
private DataGridState<T> state;
private ToolbarTag toolbar;
private int totalNumberOfItems;
private String value;
private String var;
private ViewDef viewDef;
private String groupExpression;
private String prevGroupValue;
private String title;
private DataGridHeader headerBody;
public DataGridTag() {
allColumns = Lists.newArrayList();
appliedFilters = Lists.newArrayList();
columnGroups = Lists.newArrayList();
displayedColumns = Maps.newLinkedHashMap();
hiddenColumns = Lists.newArrayList();
illegalState = false;
var = "row";
viewDef = new ViewDef();
selectAllRows = false;
}
@Override
public String execute() throws Exception {
if (executed && isShouldRefreshGrid()) {
return JSP_LOCATION;
} else if (executed && !isShouldRefreshGrid()) {
return null;
}
executed = true;
this.state = (DataGridState<T>) getValueStack().findValue(value);
Preconditions.checkNotNull(state, "The value for the datagrid with value %s was null. "
+ "A datagrid must have a value in order to be displayed.", value);
if (state.getItemProvider() != null && itemProvider == null) {
this.itemProvider = state.getItemProvider();
}
Preconditions.checkNotNull(itemProvider, "The item provider for the datagrid with value %s was null. "
+ "A datagrid must have an itemProvider to display items.", value);
if (state.isSearchCleared()) {
state.setSearchCleared(false);
state.setSearch("");
state.getFilters().clear();
}
if (state.isClearSelected()) {
state.setClearSelected(false);
state.clearSelectedItems();
state.getSelectedRowId().clear();
}
if (state.getFilters().isModified()) {
state.setPage(1);
state.getFilters().setModified(false);
}
allColumns.addAll(mergeColumns().values());
Collections.sort(allColumns);
for (DataGridColumnTag column : allColumns) {
column.execute();
if (column.isDisplayed()) {
displayedColumns.put(column.getCode(), column);
} else {
hiddenColumns.add(column);
}
}
// add applied filters
for (Entry<String, String> filter : state.getFilters().entries()) {
if (!filter.getKey().equals("scopePath")) {
appliedFilters.add(Maps.immutableEntry(filter.getKey(), getFilterValue(filter.getKey(), filter.getValue())));
}
}
toolbar = Iterables.getOnlyElement(getChildren(ToolbarTag.class), null);
setHeaderBody(Iterables.getOnlyElement(getChildren(DataGridHeader.class), null));
// fetch items and counts
try {
if (!isShouldRefreshGrid() && itemProvider instanceof SelectableItemProvider) {
selectRows();
} else {
currentPage = itemProvider.getPage(this);
updateSelectedRows();
totalNumberOfItems = itemProvider.getTotalNumberOfItems(this);
if (currentPage.isEmpty() && totalNumberOfItems > 0) {
state.setPage(Integer.valueOf(1));
currentPage = itemProvider.getPage(this);
}
}
if (!isShouldRefreshGrid()) {
return null;
}
} catch (IllegalStateException e) {
illegalState = true;
errorMessage = e.getMessage();
}
return JSP_LOCATION;
}
private Map<String, DataGridColumnTag> mergeColumns() throws Exception {
Map<String, DataGridColumnTag> columns = Maps.newHashMap();
if (viewDef != null) {
for (ViewField field : viewDef.getFields()) {
DataGridColumnTag column = new DataGridColumnTag();
column.setJspContext(getJspContext());
column.setParent(this);
column.setCode(field.getCode());
column.setName(field.getName());
column.setRequired(field.isRequired());
column.setField(field);
column.setDisplayOrder(Integer.toString(field.getDisplayOrder()));
columns.put(field.getCode(), column);
}
}
List<DataGridColumnTag> hardCodedColumns = Lists.newArrayList();
// Get all of the columns that are not a part of groups
hardCodedColumns.addAll(getChildren(DataGridColumnTag.class));
// Get all of the columns that are a part of groups
for (DataGridColumnGroupTag group : getChildren(DataGridColumnGroupTag.class)) {
group.execute();
columnGroups.add(group);
hardCodedColumns.addAll(group.getColumns());
}
int columnIndex = 0;
for (DataGridColumnTag columnTag : hardCodedColumns) {
columnTag.setPageOrder(Integer.valueOf(columnIndex++));
log.debug("Processing Column: {}", columnTag.getCode());
DataGridColumnTag viewDefTag = columns.get(columnTag.getCode());
if (viewDefTag != null && viewDefTag.getField() != null) {
ViewField field = viewDefTag.getField();
// The column definition from the ViewDef has been overriden in the JSP
mergeColumn(columnTag, field);
// Replace the ViewDef column with the hard coded column
columns.put(columnTag.getCode(), columnTag);
} else {
// The column is hard coded in the JSP and is not in the ViewDef
columns.put(columnTag.getCode(), columnTag);
}
}
return columns;
}
private void mergeColumn(DataGridColumnTag hardCoded, ViewField viewField) {
if (StringUtils.isBlank(hardCoded.getName())) {
// If the name was not specified then use the one from the ViewDef
hardCoded.setName(viewField.getName());
}
if (StringUtils.isBlank(hardCoded.getDisplayOrder())) {
// If the order was not specified then use the one from the ViewDef
if (viewField == null) {
log.debug("Column: {} - {}", hardCoded.getCode(), hardCoded.getDisplayOrder());
log.debug("Column Parent: {}", hardCoded.getParent());
} else {
hardCoded.setDisplayOrder(Integer.toString(viewField.getDisplayOrder()));
}
}
}
public boolean isShouldRefreshGrid() {
return !"false".equals(getRequest().getParameter("refreshGrid"));
}
public String getAction() {
if (action == null) {
ActionInvocation ai = (ActionInvocation) ActionContext.getContext().get(ActionContext.ACTION_INVOCATION);
action = ai.getProxy().getActionName();
}
return action;
}
public List<DataGridColumn> getAllColumns() {
return new ArrayList<DataGridColumn>(allColumns);
}
public List<Entry<String, String>> getAppliedFilters() {
return appliedFilters;
}
public List<DataGridColumnGroupTag> getColumnGroups() {
return columnGroups;
}
@Override
public List<DataGridColumn> getColumns() {
return new ArrayList<DataGridColumn>(displayedColumns.values());
}
public Iterator<RowInfo<T>> getCurrentPage() {
return Iterables.transform(currentPage, new Function<T, RowInfo<T>>() {
@Override
public RowInfo<T> apply(T row) {
RowInfo<T> info = new RowInfo<>(DataGridTag.this, row);
return info;
}
}).iterator();
}
public List<DataViewControlTag> getDataViewControls() {
return dataViewControls;
}
public String getErrorMessage() {
return errorMessage;
}
public String getFieldName() {
return fieldName;
}
@Override
public Multimap<String, String> getFilters() {
return state.getFilters();
}
public String getFilterValue(String fieldCode, String value) {
if (viewDef == null) {
return null;
}
for (ViewField field : viewDef.getFields()) {
if (field.getCode().equals(fieldCode)) {
return field.getOptions().isEmpty() ? value : field.getOptions().get(value);
}
}
return value;
}
public int getFirstPage() {
return Math.max(1, getPage() - 10);
}
@Override
public int getFirstResult() {
return state.getFirstResult();
}
public List<DataGridColumnTag> getHiddenColumns() {
return hiddenColumns;
}
public Set<String> getHiddenFieldsInError() {
return hiddenFieldsInError;
}
@Override
public String getId() {
return value;
}
public String getInlineSaveAction() {
return inlineSaveAction;
}
public DataGridItemProvider<?> getItemProvider() {
return itemProvider;
}
public int getLastPage() {
return Math.min(getPage() + 10, getTotalNumberOfPages());
}
public String getNamespace() {
if (namespace == null) {
ActionInvocation ai = (ActionInvocation) ActionContext.getContext().get(ActionContext.ACTION_INVOCATION);
namespace = ai.getProxy().getNamespace();
}
return namespace;
}
public int getNumberOfColumns() {
return displayedColumns.size() + (isSelectable() ? 1 : 0) + (isInlineEditable() ? 1 : 0);
}
public int getNumberOfOuterColumns() {
return (StringUtils.isBlank(title) ? 1 : 2) + (headerBody == null ? 0 : 1);
}
@Override
public int getPage() {
return state.getPage();
}
public int getPageEnd() {
return Math.min(getPageStart() + getPageSize() - 1, getTotalNumberOfItems());
}
@Override
public int getPageSize() {
return state.getPageSize();
}
public int getPageStart() {
return (getPage() - 1) * getPageSize() + 1;
}
public String getRowId(T item) {
try {
getValueStack().push(item);
return getValueStack().findString(rowValue);
} finally {
getValueStack().pop();
}
}
public String getRowValue() {
return rowValue;
}
public String getSaveAction() {
return saveAction;
}
@Override
public String getSearch() {
return state.getSearch();
}
public String getSelectedDisabledTest() {
return selectedDisabledTest;
}
@Override
public List<T> getSelectedItems() {
return state.getSelectedItems();
}
public DataGridState<T> getState() {
if (state == null) {
state = (DataGridState<T>) getValueStack().findValue(value);
}
return state;
}
public ToolbarTag getToolbar() {
return toolbar;
}
public int getTotalNumberOfItems() {
return totalNumberOfItems;
}
public int getCurrentPageSize() {
if (currentPage != null) {
return currentPage.size();
}
return 0;
}
public int getTotalNumberOfPages() {
return (int) Math.ceil((double) getTotalNumberOfItems() / (double) getPageSize());
}
public String getValue() {
return value;
}
public String getVar() {
return var;
}
@Override
public ViewDef getViewDef() {
return viewDef;
}
public boolean isColumnSelectable() {
return columnSelectable;
}
public boolean isEditAlertDisabled() {
return editAlertDisabled;
}
public boolean isEditMode(T row) {
boolean editing;
if (multiEditable) {
editing = true;
} else if (!inlineEditable) {
editing = false;
} else {
String paramValue = state.getEditRowId();
state.setEditRowId(null);
String rowId = getRowId(row);
editing = paramValue != null && paramValue.equals(rowId);
}
if (viewDef != null && editing && !multiEditable) {
Map<String, List<String>> fieldErrors = Maps.newHashMap((Map<String, List<String>>) getValueStack().findValue(
"fieldErrors"));
List<String> displayedFieldNames = Lists.newArrayList();
for (ViewField viewField : viewDef.getFields()) {
if (displayedColumns.keySet().contains(viewField.getCode())) {
displayedFieldNames.add(var + "." + viewField.getCode());
}
}
fieldErrors.keySet().removeAll(displayedFieldNames);
hiddenFieldsInError = fieldErrors.keySet();
}
return editing;
}
public boolean isFewerPages() {
return getFirstPage() > 1;
}
public boolean isIllegalState() {
return illegalState;
}
public boolean isInlineEditable() {
return inlineEditable;
}
public boolean isInlineEditing() {
if (!inlineEditable) {
return false;
}
boolean editing = StringUtils.isNotEmpty(state.getEditRowId());
return editing;
}
public boolean isMorePages() {
return getLastPage() < getTotalNumberOfPages();
}
public boolean isMultiEditable() {
return multiEditable;
}
public boolean isNoResults() {
if (totalNumberOfItems > 0) {
return false;
}
return true;
}
public boolean isOnFirstPage() {
return getPage() == 1;
}
public boolean isOnLastPage() {
return getPage() * getPageSize() >= getTotalNumberOfItems();
}
@Override
public boolean isPaging() {
return state.isPaging();
}
public boolean isResultsHeaderShown() {
return isPaging() || isColumnSelectable();
}
public boolean isRowSelected(T row) {
if (!selectable) {
return false;
}
String representation = getRowId(row);
return state.isItemSelected(representation);
}
public boolean isSearchable() {
return searchable;
}
public boolean isSelectable() {
return selectable;
}
public boolean isSelectAllRows() {
return selectAllRows;
}
public boolean isToolbarUsed() {
return toolbar != null;
}
public void setAction(String action) {
this.action = action;
}
public void setColumnGroups(List<DataGridColumnGroupTag> columnGroups) {
this.columnGroups = columnGroups;
}
public void setColumnSelectable(boolean columnSelectable) {
this.columnSelectable = columnSelectable;
}
public void setEditAlertDisabled(boolean editAlertDisabled) {
this.editAlertDisabled = editAlertDisabled;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public void setHiddenColumns(List<DataGridColumnTag> hiddenColumns) {
this.hiddenColumns = hiddenColumns;
}
public void setInlineEditable(boolean inlineEditable) {
this.inlineEditable = inlineEditable;
}
public void setInlineSaveAction(String inlineSaveAction) {
this.inlineSaveAction = inlineSaveAction;
}
public void setItemProvider(DataGridItemProvider<T> itemProvider) {
this.itemProvider = itemProvider;
}
public void setMultiEditable(boolean multiEditable) {
this.multiEditable = multiEditable;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public void setPageSize(Integer pageSize) {
state.setPageSize(pageSize);
}
public void setPaging(boolean paging) {
getState().setPaging(paging);
}
public void setRowValue(String rowValue) {
this.rowValue = rowValue;
}
public void setSaveAction(String saveAction) {
this.saveAction = saveAction;
}
public void setSearchable(boolean searchable) {
this.searchable = searchable;
}
public void setSelectable(boolean selectable) {
this.selectable = selectable;
}
public void setSelectAllRows(boolean selectAllRows) {
this.selectAllRows = selectAllRows;
}
public void setSelectedDisabledTest(String selectedDisabledTest) {
this.selectedDisabledTest = selectedDisabledTest;
}
public void setState(DataGridState<T> state) {
this.state = state;
}
public void setValue(String value) {
this.value = value;
}
public void setVar(String var) {
this.var = var;
}
public void setViewDef(ViewDef viewDef) {
this.viewDef = viewDef;
}
/**
* update the selected rows
*
* @return true if any rows were updated
*/
public boolean updateSelectedRows() {
List<T> itemsToUpdate = Lists.newArrayList(currentPage);
itemsToUpdate.addAll(state.getSelectedItems());
boolean updated = false;
for (T item : itemsToUpdate) {
if (updateRowSelection(item)) {
updated = true;
}
}
return updated;
}
/**
* update
* @param row Object displayed in the row
*
* @return true if the selection was updated.
*/
public boolean updateRowSelection(T row) {
String representation = getRowId(row);
if (StringUtils.isBlank(representation)) {
return false;
}
Boolean selected = state.getSelectedRowId().get(representation);
if (selected != null && selected) {
state.selectItem(representation, row);
} else if (selected != null && !selected) {
state.deSelectItem(representation);
}
return true;
}
public void selectRows() {
if (itemProvider instanceof SelectableItemProvider) {
SelectableItemProvider<T> sip = (SelectableItemProvider<T>) itemProvider;
for (Entry<String, Boolean> selection : state.getSelectedRowId().entrySet()) {
if (selection.getValue()) {
state.selectItem(selection.getKey(), sip.getObjectForKey(selection.getKey()));
} else {
state.deSelectItem(selection.getKey());
}
}
state.getSelectedRowId().clear();
}
}
public boolean dataFiltered(String id) {
return state.getFilters().asMap().containsKey(id);
}
public void setShowRequired(boolean showRequired) {
this.showRequired = showRequired;
}
public boolean isShowRequired() {
return showRequired;
}
public String getGroupExpression() {
return groupExpression;
}
public void setGroupExpression(String groupExpression) {
this.groupExpression = groupExpression;
}
public String getPrevGroupValue() {
return prevGroupValue;
}
public void setPrevGroupValue(String prevGroupValue) {
this.prevGroupValue = prevGroupValue;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public DataGridHeader getHeaderBody() {
return headerBody;
}
public void setHeaderBody(DataGridHeader headerBody) {
this.headerBody = headerBody;
}
}