/* | |
* 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.empire.jsf2.pageelements; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
import javax.faces.event.ValueChangeEvent; | |
import org.apache.commons.beanutils.BeanUtils; | |
import org.apache.empire.commons.ObjectUtils; | |
import org.apache.empire.commons.StringUtils; | |
import org.apache.empire.data.Column; | |
import org.apache.empire.data.DataType; | |
import org.apache.empire.db.DBColumn; | |
import org.apache.empire.db.DBColumnExpr; | |
import org.apache.empire.db.DBCommand; | |
import org.apache.empire.db.DBDatabaseDriver; | |
import org.apache.empire.db.DBDriverFeature; | |
import org.apache.empire.db.DBReader; | |
import org.apache.empire.db.DBRecordData; | |
import org.apache.empire.db.DBRowSet; | |
import org.apache.empire.db.exceptions.NoPrimaryKeyException; | |
import org.apache.empire.db.expr.order.DBOrderByExpr; | |
import org.apache.empire.exceptions.InternalException; | |
import org.apache.empire.exceptions.InvalidArgumentException; | |
import org.apache.empire.exceptions.MiscellaneousErrorException; | |
import org.apache.empire.exceptions.NotSupportedException; | |
import org.apache.empire.exceptions.ObjectNotValidException; | |
import org.apache.empire.exceptions.UnexpectedReturnValueException; | |
import org.apache.empire.jsf2.app.FacesUtils; | |
import org.apache.empire.jsf2.pages.Page; | |
import org.apache.empire.jsf2.utils.ListColumnFinder; | |
import org.apache.empire.jsf2.utils.ListItemSelection; | |
import org.apache.empire.jsf2.utils.ParameterMap; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class BeanListPageElement<T> extends ListPageElement<T> implements ListItemSelection | |
{ | |
private static final long serialVersionUID = 1L; | |
private static final Logger log = LoggerFactory.getLogger(BeanListPageElement.class); | |
public static final String NO_RESULT_ATTRIBUTE = "noQueryResult"; | |
private ListTableInfo listTableInfo = null; | |
protected DBRowSet rowset; | |
protected Column defaultSortColumn; | |
protected boolean defaultSortAscending = true; | |
protected DBOrderByExpr secondarySortOrder = null; | |
protected int maxItemCount = 1000; | |
/** | |
* Extended ListTableInfo | |
*/ | |
public static class BeanListTableInfo extends ListTableInfo | |
{ | |
private static final long serialVersionUID = 1L; | |
private DBCommand queryCmd = null; | |
public DBCommand getQueryCmd() | |
{ | |
return queryCmd; | |
} | |
public void setQueryCmd(DBCommand queryCmd) | |
{ | |
this.queryCmd = queryCmd; | |
} | |
} | |
/** | |
* Constructor for creating a BeanListPageElement | |
* @param page the page element | |
* @param beanClass the bean class | |
* @param rowset required Table or View | |
* @param defaultSortColumn sort column that must belong to rowset | |
* @param propertyName the property name which is used to get and retrieve session information | |
*/ | |
public BeanListPageElement(Page page, Class<T> beanClass, DBRowSet rowset, DBColumn defaultSortColumn, String propertyName) | |
{ | |
super(page, beanClass, propertyName); | |
// Check | |
if (rowset == null) | |
throw new InvalidArgumentException("rowset", rowset); | |
// Default Sort Order | |
if (defaultSortColumn!=null) | |
{ // Date sort order is descending by default | |
if (defaultSortColumn.getRowSet()!=rowset) | |
throw new InvalidArgumentException("defaultSortColumn", defaultSortColumn); | |
if (defaultSortColumn.getDataType().isDate()) | |
defaultSortAscending = false; | |
} | |
// Set Bean Class and more | |
this.rowset = rowset; | |
this.defaultSortColumn = defaultSortColumn; | |
} | |
/** | |
* Overload that requires a default sort order to be provided | |
* @param page the page element | |
* @param beanClass the bean class | |
* @param defaultSortColumn | |
* @param propertyName the property name which is used to get and retrieve session information | |
*/ | |
public BeanListPageElement(Page page, Class<T> beanClass, DBColumn defaultSortColumn, String propertyName) | |
{ | |
this(page, beanClass, defaultSortColumn.getRowSet(), defaultSortColumn, propertyName); | |
} | |
/** | |
* Overload that requires a default sort order to be provided | |
* @param page the page element | |
* @param beanClass the bean class | |
* @param defaultSortColumn | |
*/ | |
public BeanListPageElement(Page page, Class<T> beanClass, DBColumn defaultSortColumn) | |
{ | |
this(page, beanClass, defaultSortColumn.getRowSet(), defaultSortColumn, getDefaultPropertyName(defaultSortColumn.getRowSet())); | |
} | |
/** | |
* Overload that requires a default sort order to be provided | |
* @param page the page element | |
* @param beanClass the bean class | |
* @param rowset required Table or View | |
*/ | |
public BeanListPageElement(Page page, Class<T> beanClass, DBRowSet rowSet) | |
{ | |
this(page, beanClass, rowSet, null, getDefaultPropertyName(rowSet)); | |
} | |
@Override | |
protected void onInitPage() | |
{ | |
ListTableInfo lti = getTableInfo(); | |
if (lti.isValid() && items == null) | |
{ // loadBookings | |
loadItems(true); | |
} | |
} | |
@Override | |
protected void onRefreshPage() | |
{ | |
ListTableInfo lti = getTableInfo(); | |
if (lti.isValid() && lti.isModified()) | |
{ // load | |
loadItems(false); | |
} | |
else | |
{ // hide the loading indicator on client (JavaScript call) | |
if (lti.isAllowPagination()) | |
updateScrollbar(); | |
} | |
} | |
@Override | |
public int getItemCount() | |
{ | |
return getTableInfo().getItemCount(); | |
} | |
public Column getDefaultSortColumn() | |
{ | |
return defaultSortColumn; | |
} | |
public boolean isDefaultSortAscending() | |
{ | |
return defaultSortAscending; | |
} | |
public void setDefaultSortAscending(boolean defaultSortAscending) | |
{ | |
this.defaultSortAscending = defaultSortAscending; | |
} | |
public DBOrderByExpr getSecondarySortOrder() | |
{ | |
return secondarySortOrder; | |
} | |
public void setSecondarySortOrder(DBOrderByExpr secondarySortOrder) | |
{ | |
this.secondarySortOrder = secondarySortOrder; | |
} | |
/** session scoped properties **/ | |
@Override | |
public ListTableInfo getTableInfo() | |
{ | |
// Lazy initialization | |
if ((listTableInfo == null) && (listTableInfo = getSessionObject(ListTableInfo.class)) == null) | |
{ // Create and put on session | |
listTableInfo = new BeanListTableInfo(); | |
listTableInfo.setSortColumnName((defaultSortColumn!=null ? defaultSortColumn.getName() : null)); | |
listTableInfo.setSortAscending(defaultSortAscending); | |
setSessionObject(ListTableInfo.class, listTableInfo); | |
} | |
return listTableInfo; | |
} | |
@Override | |
public void clearItems() | |
{ // clear Items | |
super.clearItems(); | |
setSessionObject(ListTableInfo.class, null); | |
listTableInfo = null; | |
// Clear parameters | |
getParameterMap().clear(rowset); | |
} | |
protected ParameterMap getParameterMap() | |
{ | |
return FacesUtils.getParameterMap(FacesUtils.getContext()); | |
} | |
/** | |
* Init list items with pagination | |
* | |
* @param queryCmd | |
* @param pageSize | |
*/ | |
public void initItems(DBCommand queryCmd, DBCommand countCmd, int pageSize) | |
{ | |
clearItems(); | |
// Init List Table Info | |
BeanListTableInfo lti = (BeanListTableInfo) getTableInfo(); | |
lti.setQueryCmd(queryCmd); | |
if (pageSize > 0) | |
{ // Negative count means: loadItems should load all items. | |
countCmd.clearSelect(); | |
countCmd.select(rowset.count()); | |
int count = rowset.getDatabase().querySingleInt(countCmd.getSelect(), countCmd.getParamValues(), 0, getConnection(rowset)); | |
lti.init(count, pageSize); | |
} | |
else | |
{ // 0 or more items available | |
lti.init(-1, 0); | |
} | |
// Init List table Info | |
lti.setSortOrderChanged(true); | |
loadItems(true); | |
// log | |
int count = getTableInfo().getItemCount(); | |
if (count==0) | |
handleNoResult(); | |
// log | |
log.info("ItemList initialized for {} item count is {}.", getPropertyName(), count); | |
} | |
/** | |
* handle the case of an empty query result | |
*/ | |
protected void handleNoResult() | |
{ | |
FacesUtils.setRequestAttribute(FacesUtils.getContext(), NO_RESULT_ATTRIBUTE, true); | |
} | |
/** | |
* Init list items without pagination | |
* | |
* @param queryCmd | |
*/ | |
public final void initItems(DBCommand queryCmd, int pageSize) | |
{ | |
DBCommand countCmd = queryCmd.clone(); | |
initItems(queryCmd, countCmd, 0); | |
} | |
/** | |
* Init list items without pagination | |
* | |
* @param queryCmd | |
*/ | |
public final void initItems(DBCommand queryCmd) | |
{ | |
initItems(queryCmd, 0); | |
} | |
/** | |
* Returns true if (and only if) items are valid but empty | |
* @return | |
*/ | |
public boolean isResultEmpty() | |
{ | |
Object noQueryResult = FacesUtils.getRequestAttribute(FacesUtils.getContext(), NO_RESULT_ATTRIBUTE); | |
return (noQueryResult!=null) ? ObjectUtils.getBoolean( noQueryResult ) : false; | |
} | |
/** | |
* loads all visible list items from the database | |
* @param initScrollbar | |
*/ | |
protected void loadItems(boolean initScrollbar) | |
{ | |
// DBReader | |
BeanListTableInfo lti = (BeanListTableInfo) getTableInfo(); | |
DBReader r = new DBReader(); | |
try | |
{ // Check command | |
DBCommand queryCmd = lti.getQueryCmd(); | |
if (queryCmd == null) | |
throw new ObjectNotValidException(this); | |
boolean loadPageFromPosition = lti.isValid() && lti.isAllowPagination(); | |
lti.setValid(false); | |
if (lti.isSortOrderChanged()) | |
{ // Set Sort order | |
setOrderBy(queryCmd); | |
lti.setSortOrderChanged(false); | |
} | |
int position = 0; | |
int skipRows = 0; | |
int maxItems = maxItemCount; | |
if (loadPageFromPosition) | |
{ // detect position | |
position = lti.getPosition(); | |
if (position > lti.getItemCount() - lti.getPageSize()) | |
{ // position > count of entries is not possible, set to max | |
position = lti.getItemCount() - lti.getPageSize(); | |
} | |
if (position < 0) | |
{ // position < 0 is not possible, set to 0 | |
position = 0; | |
} | |
// maxItems | |
maxItems = lti.getPageSize(); | |
skipRows = position; | |
// constraint | |
queryCmd.clearLimit(); | |
DBDatabaseDriver driver = queryCmd.getDatabase().getDriver(); | |
if (driver.isSupported(DBDriverFeature.QUERY_LIMIT_ROWS)) | |
{ // let the database limit the rows | |
if (driver.isSupported(DBDriverFeature.QUERY_SKIP_ROWS)) | |
{ // let the database skip the rows | |
queryCmd.skipRows(skipRows); | |
skipRows = 0; | |
} | |
queryCmd.limitRows(skipRows+maxItems); | |
} | |
} | |
// DBReader.open must always be surrounded with a try {} finally {} block! | |
r.open(queryCmd, getConnection(queryCmd)); | |
// get position from the session | |
if (skipRows>0) | |
{ // we are not at position 0, "skipping" entries | |
r.skipRows(skipRows); | |
} | |
// Read all Items | |
items = r.getBeanList(beanClass, maxItems); | |
if (items == null) | |
throw new UnexpectedReturnValueException(items, "DBReader.getBeanList"); | |
generateIdParams(rowset, items); | |
assignSelectionMap(items); | |
// set position at session object | |
if (loadPageFromPosition) | |
{ // set valid | |
if (position + items.size() > lti.getItemCount()) | |
{ // Oops: More items than expected. | |
log.warn("Item count of {} has changed. Adjusting item count.", getPropertyName()); | |
lti.init(position + items.size(), lti.getPageSize()); | |
} | |
lti.setPosition(position); | |
lti.setModified(false); | |
lti.setValid(true); | |
} | |
else | |
{ // Init the list | |
lti.init(items.size(), 0); | |
} | |
} | |
catch (RuntimeException e) | |
{ | |
log.error("Error loading bean list " + e.getMessage(), e); | |
throw e; | |
} | |
finally | |
{ | |
r.close(); | |
// Pagination | |
if (lti.isAllowPagination()) | |
{ // Scrollbar | |
if (initScrollbar) | |
initScrollbar(); | |
else | |
updateScrollbar(); | |
} | |
} | |
} | |
/** | |
* set order by for db queries | |
* | |
* @param cmd | |
*/ | |
protected void setOrderBy(DBCommand cmd) | |
{ | |
cmd.clearOrderBy(); | |
String sortColumnName = getTableInfo().getSortColumnName(); | |
boolean sortAscending = getTableInfo().getSortAscending(); | |
// Find Column | |
DBColumnExpr sortColumn = rowset.getColumn(sortColumnName); | |
if (sortColumn == null && (getPage() instanceof ListColumnFinder)) | |
{ // Ask Page | |
sortColumn = ((ListColumnFinder)getPage()).findColumn(sortColumnName); | |
} | |
if (sortColumn == null) | |
{ | |
log.error("Invalid Sort Column {}. Using Default!", sortColumnName); | |
sortColumn = (DBColumn) getDefaultSortColumn(); | |
if (sortColumn!=null) | |
getTableInfo().setSortColumnName(sortColumn.getName()); | |
} | |
// set Order | |
setOrderBy(cmd, sortColumn, sortAscending); | |
} | |
protected void setOrderBy(DBCommand cmd, DBColumnExpr sortColumn, boolean sortAscending) | |
{ | |
if (sortColumn!=null) | |
{ // Sort now | |
if (sortAscending) | |
{ | |
cmd.orderBy(sortColumn); | |
} | |
else | |
{ | |
cmd.orderBy(sortColumn.desc()); | |
} | |
} | |
// Secondary sort | |
if (this.secondarySortOrder != null && !secondarySortOrder.getColumn().equals(sortColumn)) | |
{ | |
cmd.orderBy(secondarySortOrder); | |
} | |
} | |
/* Scrollbar relacted functions */ | |
/** | |
* addJavascriptCall to initScrollbar | |
*/ | |
public void initScrollbar() | |
{ | |
ListTableInfo lti = getTableInfo(); | |
// init Scrollbar on page | |
int max = lti.getItemCount() - lti.getPageSize(); | |
if (max < 0) | |
max = 0; | |
int pos = max; | |
if (max > lti.getPosition()) | |
pos = max - lti.getPosition(); | |
// init now | |
StringBuilder b = new StringBuilder(); | |
b.append("initScrollbar('"); | |
b.append(String.valueOf(getPropertyName())); | |
b.append("',"); | |
b.append(String.valueOf(max)); | |
b.append(","); | |
b.append(String.valueOf(pos)); | |
b.append(","); | |
b.append(String.valueOf(lti.getPageSize())); | |
b.append(");"); | |
getPage().addJavascriptCall(b.toString()); | |
} | |
/** | |
* addJavascriptCall to updateScrollbar | |
*/ | |
public void updateScrollbar() | |
{ | |
int pos = getScrollbarPosition(); | |
StringBuilder b = new StringBuilder(); | |
b.append("updateScrollbar('"); | |
b.append(String.valueOf(getPropertyName())); | |
b.append("',"); | |
b.append(String.valueOf(pos)); | |
b.append(");"); | |
getPage().addJavascriptCall(b.toString()); | |
} | |
public int getScrollbarPosition() | |
{ | |
ListTableInfo lti = getTableInfo(); | |
if (!lti.isValid()) | |
return 0; | |
if (lti.getItemCount() <= lti.getPageSize()) | |
return 0; | |
int pos = lti.getItemCount() - lti.getPageSize() - lti.getPosition(); | |
return (pos >= 0 ? pos : 0); | |
} | |
public void setScrollbarPosition(int value) | |
{ | |
// should never come here! | |
} | |
public void positionValueChanged(ValueChangeEvent ve) | |
{ | |
ListTableInfo lti = getTableInfo(); | |
int val = ObjectUtils.getInteger(ve.getNewValue()); | |
int pos = lti.getItemCount() - lti.getPageSize() - val; | |
lti.setPosition((pos > 0 ? pos : 0)); | |
} | |
// @Override | |
public Set<Object[]> getSelectedItemKeys() | |
{ | |
if (selectedItems == null) | |
return null; | |
// Get the set | |
Set<Object[]> items = new HashSet<Object[]>(selectedItems.size()); | |
for (String idParam : selectedItems) | |
{ | |
Object[] key = getParameterMap().getKey(rowset, idParam); | |
if (key == null) | |
{ | |
log.warn("Object does not exist in ParameterMap!"); | |
continue; | |
} | |
items.add(key); | |
} | |
return items; | |
} | |
public void setSelectedItems(Set<Object[]> items) | |
{ | |
if (selectedItems == null) | |
throw new NotSupportedException(this, "setSelectedItems"); | |
// Get the set | |
selectedItems = new SelectionSet(items.size()); | |
for (Object[] key : items) | |
{ | |
if (key == null || key.length == 0) | |
{ | |
log.warn("Cannot select Null-Object."); | |
continue; | |
} | |
String idParam = getParameterMap().put(rowset, key); | |
selectedItems.add(idParam); | |
} | |
} | |
protected void generateIdParams(DBRowSet rowset, List<?> items) | |
{ | |
DBColumn[] keyCols = rowset.getKeyColumns(); | |
if (keyCols == null) | |
return; // No Primary Key! | |
// generate all | |
for (Object item : items) | |
{ | |
if (!(item instanceof ParameterizedItem)) | |
continue; | |
// set the idParam | |
Object[] key = getItemKey(keyCols, item); | |
String idparam = getParameterMap().put(rowset, key); | |
((ParameterizedItem)item).setIdParam(idparam); | |
} | |
} | |
protected Object[] getItemKey(DBColumn[] cols, Object item) | |
{ | |
Object[] key = new Object[cols.length]; | |
for (int i = 0; i < cols.length; i++) | |
{ | |
if (item instanceof DBRecordData) | |
{ | |
key[i] = ((DBRecordData) item).getValue(cols[i]); | |
} | |
else | |
{ // Bean Property Name | |
String propName = cols[i].getBeanPropertyName(); | |
if (propName == null || propName.length() == 0) | |
throw new MiscellaneousErrorException("Invalid Bean Property Name"); | |
// Get Property value | |
try | |
{ | |
key[i] = BeanUtils.getSimpleProperty(item, propName); | |
} | |
catch (Exception e) | |
{ | |
String msg = "Error getting property '" + propName + "' from bean."; | |
log.error(msg, e); | |
throw new InternalException(e); | |
} | |
} | |
} | |
return key; | |
} | |
@Override | |
public DBCommand getItemQueryCmd() | |
{ | |
BeanListTableInfo lti = (BeanListTableInfo) getTableInfo(); | |
DBCommand cmd = lti.getQueryCmd().clone(); | |
Set<Object[]> items = getSelectedItemKeys(); | |
if (items.size()>0) | |
{ | |
DBColumn[] pk = rowset.getKeyColumns(); | |
DBColumnExpr keyExpr = pk[0]; | |
for (int i=1; i<pk.length; i++) | |
{ | |
keyExpr = keyExpr.append(pk[i]); | |
} | |
String[] keys = new String[items.size()]; | |
int i = 0; | |
for (Object[] item : items) | |
{ | |
keys[i++] = StringUtils.arrayToString(item, ""); | |
} | |
if (isInvertSelection()) | |
cmd.where(keyExpr.notIn(keys)); | |
else | |
cmd.where(keyExpr.in(keys)); | |
} | |
// clear previous settings without the where causes | |
cmd.clearSelect(); | |
cmd.clearGroupBy(); | |
// clear skip and limit! | |
cmd.clearLimit(); | |
return cmd; | |
} | |
} |