/*
* Copyright 2009-2011 the original author or authors.
*
* Licensed 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.jdal.vaadin.data;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdal.beans.PropertyUtils;
import org.jdal.dao.Dao;
import org.jdal.dao.Page;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.dao.DataAccessException;
import com.vaadin.data.Buffered;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Container.Sortable;
import com.vaadin.data.Item;
import com.vaadin.data.Item.PropertySetChangeListener;
import com.vaadin.data.Property;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.BeanItem;
/**
* <p>
* An adapter to use PageableDataSources as Vaadin Container.
* Use a integer as itemId and load data by page from data source on
* request.
* </p>
*
* @author Jose Luis Martin - (jlm@joseluismartin.info)
*/
public class ContainerDataSource<T> implements Container, Sortable, Indexed,
ItemSetChangeNotifier, PropertySetChangeListener, Buffered {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(ContainerDataSource.class);
private Page<T> page = new Page<T>();
private Dao<T, ?extends Serializable> service;
private List<String> sortableProperties;
private List<BeanItem<T> > items = new ArrayList<BeanItem<T>>();
private Class<T> entityClass;
private List<ItemSetChangeListener> listeners = new ArrayList<ItemSetChangeListener>();
private ItemIdStrategy itemIdStrategy;
private Map<Object, Item> dirtyItems = new HashMap<Object, Item>();
private Map<Object, Item> newItems = new HashMap<Object,Item>();
private boolean readThrough = false;
private boolean writeThrough= false;
private String sortProperty;
public ContainerDataSource() {
this(null, null);
}
public ContainerDataSource(Class<T> entityClass) {
this(entityClass, null);
}
public ContainerDataSource(Dao<T, ?extends Serializable> service) {
this.service = service;
this.entityClass = service.getEntityClass();
setItemIdStrategy(new IndexedItemIdStrategy());
}
public ContainerDataSource(Class<T> entityClass, Dao<T, ?extends Serializable> service) {
this.service = service;
this.entityClass = entityClass;
setItemIdStrategy(new IndexedItemIdStrategy());
}
public void init() {
page.setSortName(getSortProperty());
loadPage();
}
/**
* {@inheritDoc}
*/
public Object nextItemId(Object itemId) {
return isLastId(itemId) ? null : getIdByIndex(indexOfId(itemId) + 1);
}
/**
* {@inheritDoc}
*/
public Object prevItemId(Object itemId) {
return isFirstId(itemId) ? null : getIdByIndex(indexOfId(itemId) - 1);
}
/**
* {@inheritDoc}
*/
public Object firstItemId() {
return itemIdStrategy.firstItemId();
}
/**
* {@inheritDoc}
*/
public Object lastItemId() {
return itemIdStrategy.lastItemId();
}
/**
* {@inheritDoc}
*/
public boolean isFirstId(Object itemId) {
return indexOfId(itemId) == 0;
}
/**
* {@inheritDoc}
*/
public boolean isLastId(Object itemId) {
return indexOfId(itemId) == page.getCount() - 1;
}
/**
* {@inheritDoc}
*/
public Object addItemAfter(Object previousItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new Items after to container");
}
/**
* {@inheritDoc}
*/
public Item addItemAfter(Object previousItemId, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new records after to container");
}
/**
* {@inheritDoc}
*/
public int indexOfId(Object itemId) {
return itemIdStrategy.indexOfId(itemId);
}
/**
* {@inheritDoc}
*/
public Object getIdByIndex(int index) {
return itemIdStrategy.getIdByIndex(index);
}
/**
* {@inheritDoc}
*/
public Object addItemAt(int index) throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new records to container");
}
/**
* {@inheritDoc}
*/
public Item addItemAt(int index, Object newItemId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new records to container");
}
/**
* {@inheritDoc}
*/
public void sort(Object[] propertyId, boolean[] ascending) {
// only use the first property :I
page.setSortName(propertyId[0].toString());
page.setOrder(ascending[0] ? Page.Order.ASC : Page.Order.DESC);
loadPage();
fireItemSetChange();
}
/**
* {@inheritDoc}
*/
public Collection<?> getSortableContainerPropertyIds() {
if (sortableProperties != null)
return sortableProperties;
if (entityClass != null) {
List<String> properties = new LinkedList<String>();
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(entityClass);
for (PropertyDescriptor pd : pds)
properties.add(pd.getName());
return properties;
}
// if we have data will try introspection
if (page.getData().size() > 0) {
BeanItem<T> item = items.get(0);
return item.getItemPropertyIds();
}
return new LinkedList<Object>();
}
/**
* {@inheritDoc}
*/
public Item getItem(Object itemId) {
return itemIdStrategy.getItem(itemId);
}
public int getPageContaining(int index) {
return index/page.getPageSize() + 1;
}
/**
* {@inheritDoc}
*/
public Collection<?> getContainerPropertyIds() {
BeanItem<T> item = newItem();
if (item != null)
return item.getItemPropertyIds();
// if we have data will try introspection
if (page.getData().size() > 0)
return items.get(0).getItemPropertyIds();
// this is an error...
log.error("Can't get property ids from entityClass or data");
return new LinkedList<Object>();
}
/**
* {@inheritDoc}
*/
public Collection<?> getItemIds() {
return itemIdStrategy.getItemIds();
}
/**
* {@inheritDoc}
*/
public Property getContainerProperty(Object itemId, Object propertyId) {
Item item = getItem(itemId);
return item != null ? item.getItemProperty(propertyId) : null;
}
/**
* {@inheritDoc}
*/
public Class<?> getType(Object propertyId) {
if (entityClass != null) {
return PropertyUtils.getPropertyDescriptor(entityClass, (String)propertyId)
.getPropertyType();
}
// if we have data will try introspection
if (page.getData().size() > 0) {
BeanItem<T> item = items.get(0);
return item.getBean().getClass();
}
return Object.class;
}
/**
* {@inheritDoc}
*/
public int size() {
return page.getCount();
}
/**
* {@inheritDoc}
*/
public boolean containsId(Object itemId) {
return itemIdStrategy.containsId(itemId);
}
/**
* {@inheritDoc}
*/
public Item addItem(Object itemId) throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new records to container");
}
/**
* {@inheritDoc}
*/
public Object addItem() throws UnsupportedOperationException {
BeanItem<T> newItem = newItem();
if (newItem != null) {
newItem.addListener(this);
newItems.put(newItem.getBean(), newItem);
}
return newItem;
}
private BeanItem<T> newItem() {
T bean = null;
try {
bean = BeanUtils.instantiate(entityClass);
} catch (BeanInstantiationException be) {
log.error(be);
return null;
}
BeanItem<T> newItem = new BeanItem<T>(bean);
return newItem;
}
/**
* {@inheritDoc}
*/
public boolean removeItem(Object itemId) {
if (!containsId(itemId))
return false;
int index = (Integer) itemId;
if (isInPage(index)) {
service.delete(page.getData().get(globalToPage(index)));
loadPage();
}
else {
Page<T> oneItem = new Page<T>(1, index);
oneItem.setFilter(page.getFilter());
service.getPage(oneItem);
service.delete(oneItem.getData().get(0));
page.setCount(page.getCount() - 1);
}
return true;
}
/**
* {@inheritDoc}
*/
public boolean addContainerProperty(Object propertyId, Class<?> type,
Object defaultValue) throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new properties to container");
}
/**
* {@inheritDoc}
*/
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
throw new UnsupportedOperationException("ContainerDataSourceAdapter don't support adding new properties to container");
}
/**
* {@inheritDoc}
*/
public boolean removeAllItems() throws UnsupportedOperationException {
try {
Page<T> all = page.clone();
all.setPageSize(Integer.MAX_VALUE);
service.delete(all.getData());
} catch (DataAccessException dae) {
return false;
}
return true;
}
private void loadPage() {
service.getPage(page);
int index = 0;
items.clear();
for (T t : page.getData()) {
BeanItem<T> item = getDirtyOrCreate(t);
item.addListener(this);
items.add(item);
itemIdStrategy.itemLoaded(pageToGlobal(index++), item);
}
}
/**
* @param t
* @return bean item
*/
@SuppressWarnings("unchecked")
private BeanItem<T> getDirtyOrCreate(T t) {
BeanItem<T> item = new BeanItem<T>(t);
if (readThrough && dirtyItems.containsKey(t))
item = (BeanItem<T>) dirtyItems.get(t);
return item;
}
/**
* @param i
* @return
*/
private int pageToGlobal(int index) {
return page.getStartIndex() + index;
}
/**
* Convert global index to page index.
* @param index global index
* @return the index in current page
*/
private int globalToPage(int index) {
return index - page.getStartIndex();
}
private boolean isInPage(int index) {
return globalToPage(index) >= 0 && globalToPage(index) < page.getPageSize();
}
public Dao<T, ?extends Serializable> getService() {
return service;
}
public void setService(Dao<T, Serializable> service) {
this.service = service;
}
public void setPage(Page<T> page) {
this.page = page;
loadPage();
}
public List<String> getSortableProperties() {
return sortableProperties;
}
public void setSortableProperties(List<String> sortableProperties) {
this.sortableProperties = sortableProperties;
}
public void addListener(ItemSetChangeListener listener) {
if (!listeners.contains(listener))
listeners.add(listener);
}
public void removeListener(ItemSetChangeListener listener) {
listeners.remove(listener);
}
/**
* Notity listeners that the item set change
*/
private void fireItemSetChange() {
ItemSetChangeEvent isce = new ItemSetChangeEvent() {
public Container getContainer() {
return ContainerDataSource.this;
}
};
// must be first
itemIdStrategy.containerItemSetChange(isce);
for (ItemSetChangeListener listener : listeners) {
listener.containerItemSetChange(isce);
}
}
/**
* Set the page size
* @param pageSize the page size to set
*/
public void setPageSize(int pageSize) {
page.setPageSize(pageSize);
loadPage();
}
/**
* Get the page size
* @return the page size
*/
public int getPageSize() {
return page.getPageSize();
}
/**
* @return filter Object
* @see info.joseluismartin.dao.Page#getFilter()
*/
public Object getFilter() {
return page.getFilter();
}
/**
* @param filter
* @see info.joseluismartin.dao.Page#setFilter(java.lang.Object)
*/
public void setFilter(Object filter) {
page.setFilter(filter);
loadPage();
fireItemSetChange();
}
/**
* Get all keys from pageable datasource
*/
public List<Serializable> getKeys() {
Page<T> p = new Page<T>(Integer.MAX_VALUE);
p.setFilter(page.getFilter());
p.setSortName(page.getSortName());
p.setOrder(page.getOrder());
return service.getKeys(p);
}
/**
* @return the itemIdStrategy
*/
public ItemIdStrategy getItemIdStrategy() {
return itemIdStrategy;
}
/**
* @param itemIdStrategy the itemIdStrategy to set
*/
public void setItemIdStrategy(ItemIdStrategy itemIdStrategy) {
this.itemIdStrategy = itemIdStrategy;
itemIdStrategy.setContainerDataSource(this);
}
/**
* @param index
*/
public Item getItemByIndex(int index) {
if (!isInPage(index)) {
if (log.isDebugEnabled())
log.debug("Page fault on index: " + index);
page.setPage(getPageContaining(index));
loadPage();
}
int pageIndex = globalToPage(index);
return pageIndex < items.size() ? items.get(pageIndex) : null;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void itemPropertySetChange(com.vaadin.data.Item.PropertySetChangeEvent event) {
dirtyItems.put(((BeanItem<T>) event.getItem()).getBean(), event.getItem());
if (isWriteThrough())
commit();
}
/**
* Save changes to Persistent Service
* @return true if all items was updated.
*/
@SuppressWarnings("unchecked")
public boolean save() {
// insert news
for (Item i : newItems.values()) {
try {
BeanItem<T> bi = (BeanItem<T>) i;
service.save(bi.getBean());
newItems.remove(bi.getBean());
}
catch (DataAccessException dae) {
log.error(dae);
}
}
// update dirties
for (Item i : dirtyItems.values()) {
try {
BeanItem<T> bi = (BeanItem<T>) i;
service.save(bi.getBean());
dirtyItems.remove(bi.getBean());
}
catch (DataAccessException dae) {
log.error(dae);
}
}
return newItems.isEmpty() && dirtyItems.isEmpty();
}
/**
* {@inheritDoc}
*/
public void commit() throws SourceException, InvalidValueException {
if (!save()) {
// lost changes?
discard();
throw new SourceException(this);
}
}
/**
* {@inheritDoc}
*/
public void discard() throws SourceException {
newItems.clear();
dirtyItems.clear();
}
/**
* {@inheritDoc}
*/
public boolean isModified() {
return dirtyItems.size() > 0 || newItems.size() > 0;
}
/**
* {@inheritDoc}
*/
public boolean isReadThrough() {
return readThrough;
}
/**
* {@inheritDoc}
*/
public boolean isWriteThrough() {
return writeThrough;
}
/**
* {@inheritDoc}
*/
public void setReadThrough(boolean readThrough) throws SourceException {
this.readThrough = readThrough;
}
/**
* {@inheritDoc}
*/
public void setWriteThrough(boolean writeThrough) throws SourceException, InvalidValueException {
this.writeThrough = writeThrough;
}
/**
* Gets the page
* @return the page
*/
public Page<T> getPage() {
return page;
}
/**
* @return the sortProperty
*/
public String getSortProperty() {
return sortProperty;
}
/**
* @param sortProperty the sortBy to set
*/
public void setSortProperty(String sortProperty) {
this.sortProperty = sortProperty;
}
public void setBuffered(boolean buffered) {
// TODO Auto-generated method stub
}
public boolean isBuffered() {
// TODO Auto-generated method stub
return false;
}
public void addItemSetChangeListener(ItemSetChangeListener listener) {
// TODO Auto-generated method stub
}
public void removeItemSetChangeListener(ItemSetChangeListener listener) {
// TODO Auto-generated method stub
}
public List<?> getItemIds(int startIndex, int numberOfItems) {
return this.itemIdStrategy.getItemIds(startIndex, numberOfItems);
}
}