blob: 0c60ab6f4efc7cfed1e4acf375d9324db7ce49c6 [file] [log] [blame]
/*
* 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() == DataType.DATE || defaultSortColumn.getDataType() == DataType.DATETIME)
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().get(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;
}
}