I got this solved with an ItemSetChangeLIstener implementation:
public class DefaultTableSetupListener implements Container.ItemSetChangeListener {
private PropertyFilter showColumnFilter = PropertyFilter.ByPattern.createIdFilter();
private PropertyFilter sortColumnFilter = PropertyFilter.ByPattern.createIdFilter();
private Transformer headerTransform = new Transformer.Header();
private Table rowMapTable;
private LinkedHashMap<Object,Table.ColumnGenerator> columnGenerators = new LinkedHashMap<Object,Table.ColumnGenerator>();
public DefaultTableSetupListener(RowMapTable rowMapTable) {
this.rowMapTable = rowMapTable;
}
@Override
public void containerItemSetChange(Container.ItemSetChangeEvent event) {
log.info("Container.ItemSetChangeEvent");
processColumns();
}
private void processColumns() {
ArrayList<Object> visibleCols = new ArrayList<Object>();
// only process columns if container has items
if(rowMapTable.getContainerDataSource().size()==0) return;
for(Map.Entry<Object,Table.ColumnGenerator> eg:columnGenerators.entrySet()){
rowMapTable.removeGeneratedColumn(eg.getKey());
rowMapTable.addGeneratedColumn(eg.getKey(),eg.getValue());
visibleCols.add(eg.getKey());
}
Collection c = rowMapTable.getContainerPropertyIds();
Collection s = rowMapTable.getSortableContainerPropertyIds();
for (Object l : c) {
try {
visibleCols.add(l);
rowMapTable.setColumnHeader(l, headerTransform.transform(l).toString());
if(rowMapTable.isColumnCollapsingAllowed()) {
rowMapTable.setColumnCollapsed(l, !showColumnFilter.accepts(l));
}
if (sortColumnFilter.accepts(l)) {
s.add(l);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
rowMapTable.setVisibleColumns(visibleCols.toArray());
}
public PropertyFilter getShowColumnFilter() {
return showColumnFilter;
}
public void setShowColumnFilter(PropertyFilter showColumnFilter) {
this.showColumnFilter = showColumnFilter;
}
public PropertyFilter getSortColumnFilter() {
return sortColumnFilter;
}
public void setSortColumnFilter(PropertyFilter sortColumnFilter) {
this.sortColumnFilter = sortColumnFilter;
}
public Transformer getHeaderTransform() {
return headerTransform;
}
public void setHeaderTransform(Transformer headerTransform) {
this.headerTransform = headerTransform;
}
public LinkedHashMap<Object, Table.ColumnGenerator> getColumnGenerators() {
return columnGenerators;
}
public void setColumnGenerators(LinkedHashMap<Object, Table.ColumnGenerator> columnGenerators) {
this.columnGenerators = columnGenerators;
}
protected Logger log = Logger.getLogger(getClass());
}
Property Filter Interface…
public interface PropertyFilter {
boolean accepts(Object propertyId);
}
Transformer Interface to tweak column headings
public interface Transformer {
Object transform(Object in);
}
My Container implementation (This would be editable if backed by a JDBC RowSet )
import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import org.apache.commons.lang.NotImplementedException;
import org.apache.log4j.Logger;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
/**
*
*/
public class RowMapContainer
implements
ConnectionCallback<Object>,
Container, Container.Ordered,
Container.Indexed,
Container.Sortable,
Container.ItemSetChangeNotifier
{
@Autowired
private DataSource dataSource;
private SqlBuilder sqlBuilder = new PreparedSqlBuilder();
private ArrayList<String> propertyIds = new ArrayList<String>();
private LinkedHashMap<Object,RowMapItem> itemMap = new LinkedHashMap<Object,RowMapItem>();
private LinkedHashMap<String,Class> propertyTypes = new LinkedHashMap<String,Class>();
private ArrayList<Object> sortableContainerPropertyIds = new ArrayList<Object>();
private Integer size = -1;
private Integer index = 0;
private Integer pageSize = 500;
public RowMapContainer( ) {
}
public RowMapContainer(DataSource dataSource, String sql) {
this(dataSource,new PreparedSqlBuilder(sql));
}
public RowMapContainer(DataSource dataSource, SqlBuilder sqlBuilder) {
this.dataSource = dataSource;
this.sqlBuilder = sqlBuilder;
}
public void clear(){
ArrayList<String> props = new ArrayList<String>(getPropertyIds());
for(String p:props){
removeContainerProperty(p);
}
removeAllItems();
sortableContainerPropertyIds.clear();
notifyItemSetChange();
}
public void load() {
new JdbcTemplate(dataSource).execute(this);
}
@Override
public Object doInConnection(Connection c) throws SQLException, DataAccessException {
PreparedStatement stmt = sqlBuilder.buildPreparedStatement(c);
try {
stmt.setFetchSize(pageSize);
boolean go=true;
ResultSet rs = stmt.executeQuery();
updateMetaProperties(rs);
int sr = startRow();
int er = sr+pageSize;
log.info(String.format("Loading item: fetch=%d,start=%d,end=%d%n%s",pageSize,sr,er,sqlBuilder));
int row = sr;
if(sr>0) go = rs.absolute(sr);
while(go && rs.next() && row < er) {
RowMapItem rmi = itemRowMapper.mapRow(rs,row);
itemMap.put(row,rmi);
row++;
}
} finally {
stmt.close();
}
notifyItemSetChange();
return null;
}
private void updateMetaProperties(ResultSet rs) {
propertyIds.clear();
propertyTypes.clear();
sortableContainerPropertyIds.clear();
try {
ResultSetMetaData rsmd = rs.getMetaData();
for(int c=0;c<rsmd.getColumnCount();c++){
String columnName = rsmd.getColumnName(c+1);
propertyIds.add(columnName);
final Property p = getContainerProperty(new Integer(1), columnName);
propertyTypes.put(columnName, p == null ? Object.class : p.getType());
sortableContainerPropertyIds.add(columnName);
}
} catch (SQLException e) {
throw new UncategorizedSQLException("Building property ID List",null,e);
}
if(log.isDebugEnabled()){
log.debug("MetaProperties updated: "+propertyIds);
}
}
@Override
public Item getItem(Object itemId) {
return itemMap.get(itemId);
}
public RowMapItem getRowMapItem(Object itemId) {
return itemMap.get(itemId);
}
@Override
public Collection<?> getContainerPropertyIds() {
return propertyIds;
}
@Override
public Collection<?> getItemIds() {
return Collections.unmodifiableCollection(itemMap.keySet());
}
@Override
public Property getContainerProperty(Object itemId, Object propertyId) {
if (!(itemId instanceof Integer && propertyId instanceof String)) {
return null;
}
try {
RowMapItem row = itemMap.get(itemId);
return row.getItemProperty(propertyId);
} catch (final Exception e) {
return null;
}
}
@Override
public Class<?> getType(Object propertyId) {
//noinspection SuspiciousMethodCalls
Class<?> type = propertyTypes.get(propertyId);
return type==null? Object.class : type;
}
@Override
public int size() {
return itemMap.size();
}
@Override
public boolean containsId(Object itemId) {
return itemMap.containsKey(itemId);
}
@Override
public Item addItem(Object itemId) throws UnsupportedOperationException {
RowMapItem rmi = creatRowMapItem(itemId);
itemMap.put(itemId,rmi);
return rmi;
}
private RowMapItem creatRowMapItem(Object itemId) {
RowMapItem rmi = new RowMapItem(itemId,new LinkedHashMap<String,Object>());
return rmi;
}
@Override
public Object addItem() throws UnsupportedOperationException {
return addItem(size());
}
@Override
public boolean removeItem(Object itemId) throws UnsupportedOperationException {
return itemMap.remove(itemId)!=null;
}
@Override
public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException {
if(propertyId instanceof String) {
propertyIds.add((String) propertyId);
propertyTypes.put((String)propertyId,type);
} else throw new UnsupportedOperationException();
return true;
}
@SuppressWarnings({"SuspiciousMethodCalls"})
@Override
public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
propertyIds.remove(propertyId);
propertyTypes.remove(propertyId);
return true;
}
@Override
public boolean removeAllItems() throws UnsupportedOperationException {
itemMap.clear();
return true;
}
@Override
public int indexOfId(Object itemId) {
return new ArrayList<Object>(itemMap.keySet()).indexOf(itemId);
}
@Override
public Object getIdByIndex(int index) {
return new ArrayList<Object>(itemMap.keySet()).get(index);
}
@Override
public Object addItemAt(int index) throws UnsupportedOperationException {
RowMapItem rmi = (RowMapItem) addItemAt(index,index);
return rmi.getId();
}
public void addItemsAt(int index, Collection<?> objectIds){
ArrayList<Object> keys = new ArrayList<Object>(itemMap.keySet());
keys.addAll(index,objectIds);
LinkedHashMap<Object,RowMapItem> n = new LinkedHashMap<Object,RowMapItem>();
for(Object o:keys) {
RowMapItem rmi = itemMap.get(o);
if(rmi==null) rmi = creatRowMapItem(o);
n.put( o, rmi);
}
itemMap = n;
notifyItemSetChange();
}
@Override
public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
addItemsAt(index,Arrays.asList(newItemId));
return itemMap.get(newItemId);
}
@Override
public Object nextItemId(Object itemId) {
return itemMap.size();
}
@Override
public Object prevItemId(Object itemId) {
throw new NotImplementedException();
}
@Override
public Object firstItemId() {
throw new NotImplementedException();
}
@Override
public Object lastItemId() {
throw new NotImplementedException();
}
@Override
public boolean isFirstId(Object itemId) {
throw new NotImplementedException();
}
@Override
public boolean isLastId(Object itemId) {
throw new NotImplementedException();
}
@Override
public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
throw new NotImplementedException();
}
@Override
public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
throw new NotImplementedException();
}
public void setSql(String sql) throws UncategorizedSQLException {
setSqlBuilder(new PreparedSqlBuilder(sql));
}
@Override
public void sort(Object[] propertyId, boolean[]
ascending) {
if(log.isInfoEnabled()) {
//noinspection PrimitiveArrayArgumentToVariableArgMethod
log.info("Sorting container "+Arrays.asList(propertyId)
+" - "+Arrays.asList(ascending));
}
clear();
getSqlBuilder().applySortInfo(propertyId,ascending);
load();
}
@Override
public Collection<Object> getSortableContainerPropertyIds() {
return sortableContainerPropertyIds;
}
public void setSortableContainerPropertyIds(Collection<Object> ids) {
Collection<Object> s = this.sortableContainerPropertyIds;
s.clear();
for(Object o : ids) {
s.add(o);
}
}
public ArrayList<String> getPropertyIds() {
return propertyIds;
}
public LinkedHashMap<String, Class> getPropertyTypes() {
return propertyTypes;
}
public int getSize() {
return size;
}
private ArrayList<ItemSetChangeListener> itemSetChangeListeners = new ArrayList<ItemSetChangeListener>();
@Override
public void addListener(ItemSetChangeListener listener) {
itemSetChangeListeners.add(listener);
}
@Override
public void removeListener(ItemSetChangeListener listener) {
itemSetChangeListeners.remove(listener);
}
private void notifyItemSetChange() {
ItemSetChangeEvent e = new ItemSetChangeEvent(){
@Override
public Container getContainer() {
return RowMapContainer.this;
}
};
for(ItemSetChangeListener l:itemSetChangeListeners){
l.containerItemSetChange(e);
}
}
private RowMapItemMapper itemRowMapper = new RowMapItemMapper();
protected int startRow(){
return index*pageSize;
}
public SqlBuilder getSqlBuilder() {
return sqlBuilder;
}
public void setSqlBuilder(SqlBuilder sqlBuilder) {
this.sqlBuilder = sqlBuilder;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public LinkedHashMap<Object, RowMapItem> getItemMap() {
return itemMap;
}
public void setItemMap(LinkedHashMap<Object, RowMapItem> itemMap) {
this.itemMap = itemMap;
notifyItemSetChange();
}
public void setItems(List<Map<String,Object>> list) {
int r=0;
LinkedHashMap<Object, RowMapItem> itemMap = new LinkedHashMap<Object,RowMapItem>();
for(Map<String,Object> m:list) {
itemMap.put(r,new RowMapItem(r,m));
r++;
}
setItemMap(itemMap);
}
public RowMapItemMapper getItemRowMapper() {
return itemRowMapper;
}
public void setItemRowMapper(RowMapItemMapper itemRowMapper) {
this.itemRowMapper = itemRowMapper;
}
public ArrayList<ItemSetChangeListener> getItemSetChangeListeners() {
return itemSetChangeListeners;
}
public void setItemSetChangeListeners(ArrayList<ItemSetChangeListener> itemSetChangeListeners) {
this.itemSetChangeListeners = itemSetChangeListeners;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
protected static Logger log = Logger.getLogger(RowMapContainer.class);
}
ItemRowMapper to use as spring row mapper
public class RowMapItemMapper implements RowMapper<RowMapItem> {
private ColumnMapRowMapper m = new ColumnMapRowMapper();
@Override
public RowMapItem mapRow(ResultSet rs, int rowNum) throws SQLException {
Map<String,Object> r = m.mapRow(rs,rowNum);
RowMapItem row = new RowMapItem( rowNum, r );
return row;
}
}
And the ContainerItem implementation
import com.vaadin.data.Item;
import com.vaadin.data.Property;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
*
*/
public class RowMapItem implements Item {
private Object id;
private Map<String,Property> propertyMap = new LinkedHashMap<String,Property>();
private Map<String,Object> contents;
public Object getId() {
return id;
}
public void setId(Object id) {
this.id = id;
}
public Map<String, Object> getContents() {
return contents;
}
public void setContents(Map<String, Object> contents) {
this.contents = contents;
}
public RowMapItem(Object rowId, Map<String,Object> row) {
id = rowId;
contents = row;
for(String k:row.keySet()){
Object o = row.get(k);
propertyMap.put(k,new RowMapProperty( o==null?"":o ));
}
}
/**
* Adds the item property.
*
* @param id
* ID of the new Property.
* @param property
* Property to be added and associated with ID.
* @return <code>true</code> if the operation succeeded;
* <code>false</code> otherwise.
* @throws UnsupportedOperationException
* if the addItemProperty method is not supported.
*/
public boolean addItemProperty(Object id, Property property)
throws UnsupportedOperationException {
propertyMap.put( id.toString(), property );
return true;
}
/**
* Gets the property corresponding to the given property ID stored in
* the Item.
*
* @param propertyId
* identifier of the Property to get
* @return the Property with the given ID or <code>null</code>
*/
public Property getItemProperty(Object propertyId) {
if(!(propertyId instanceof String)) return null;
//noinspection SuspiciousMethodCalls
return propertyMap.get(propertyId.toString());
}
/**
* Gets the collection of property IDs stored in the Item.
*
* @return unmodifiable collection containing IDs of the Properties
* stored the Item.
*/
public Collection getItemPropertyIds() {
return Collections.unmodifiableCollection(propertyMap.keySet());
}
/**
* Removes given item property.
*
* @param id
* ID of the Property to be removed.
* @return <code>true</code> if the item property is removed;
* <code>false</code> otherwise.
* @throws UnsupportedOperationException
* if the removeItemProperty is not supported.
*/
public boolean removeItemProperty(Object id)
throws UnsupportedOperationException {
return propertyMap.remove(id.toString())!=null;
}
public class RowMapProperty implements Property {
private boolean readOnly = true;
private Object value;
public RowMapProperty(Object value) {
this(value,true);
}
private RowMapProperty(Object value,boolean readOnly) {
this.value = value;
this.readOnly = readOnly;
}
@Override
public Object getValue() {
return value;
}
@Override
public void setValue(Object newValue) throws ReadOnlyException, ConversionException {
this.value = newValue;
}
@Override
public Class<?> getType() {
return value!=null? value.getClass() : null;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean newStatus) {
this.readOnly = newStatus;
}
public String toString() {
return value==null ? "" : String.valueOf(value);
}
public Item getItem(){
return RowMapItem.this;
}
}
}