Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * 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 com.haulmont.cuba.web.gui.components; import com.haulmont.bali.util.Preconditions; import com.haulmont.chile.core.model.Instance; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.chile.core.model.MetaPropertyPath; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.MetadataTools; import com.haulmont.cuba.core.global.View; import com.haulmont.cuba.gui.components.GroupTable; import com.haulmont.cuba.gui.components.Table; import com.haulmont.cuba.gui.data.CollectionDatasource; import com.haulmont.cuba.gui.data.Datasource; import com.haulmont.cuba.gui.data.GroupDatasource; import com.haulmont.cuba.gui.data.GroupInfo; import com.haulmont.cuba.gui.data.impl.CollectionDsListenersWrapper; import com.haulmont.cuba.web.gui.data.CollectionDsWrapper; import com.haulmont.cuba.web.gui.data.ItemWrapper; import com.haulmont.cuba.web.gui.data.PropertyWrapper; import com.haulmont.cuba.web.gui.data.SortableCollectionDsWrapper; import com.haulmont.cuba.web.toolkit.data.AggregationContainer; import com.haulmont.cuba.web.toolkit.data.GroupTableContainer; import com.haulmont.cuba.web.toolkit.ui.CubaGroupTable; import com.vaadin.data.Item; import com.vaadin.server.Resource; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Element; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.stream.Collectors; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; public class WebGroupTable<E extends Entity> extends WebAbstractTable<CubaGroupTable, E> implements GroupTable<E> { protected Map<Table.Column, GroupAggregationCells> groupAggregationCells = null; protected boolean rerender = true; protected boolean showItemsCountForGroup = true; protected GroupCellValueFormatter<E> groupCellValueFormatter; public WebGroupTable() { component = createGroupTableComponent(); initComponent(component); component.setGroupPropertyValueFormatter(new AggregatableGroupPropertyValueFormatter()); } protected CubaGroupTable createGroupTableComponent() { return new CubaGroupTable() { @Override @SuppressWarnings({ "unchecked" }) public Resource getItemIcon(Object itemId) { return WebGroupTable.this.getItemIcon(itemId); } @Override protected boolean changeVariables(Map<String, Object> variables) { boolean b = super.changeVariables(variables); b = handleSpecificVariables(variables) || b; return b; } @Override public void groupBy(Object[] properties) { groupBy(properties, rerender); } }; } @Override public GroupDatasource getDatasource() { return (GroupDatasource) super.getDatasource(); } @Override protected StyleGeneratorAdapter createStyleGenerator() { return new StyleGeneratorAdapter() { @Override public String getStyle(com.vaadin.ui.Table source, Object itemId, Object propertyId) { if (!component.getGroupProperties().contains(propertyId)) { return super.getStyle(source, itemId, propertyId); } if (styleProviders != null) { return getGeneratedCellStyle(itemId, propertyId); } return null; } }; } @Override public boolean saveSettings(Element element) { if (!isSettingsEnabled()) { return false; } super.saveSettings(element); Element groupPropertiesElement = element.element("groupProperties"); if (groupPropertiesElement != null) { element.remove(groupPropertiesElement); } groupPropertiesElement = element.addElement("groupProperties"); final Collection<?> groupProperties = component.getGroupProperties(); for (Object groupProperty : groupProperties) { if (getNotCollapsedColumns().contains(getColumn(groupProperty.toString()))) { final Element groupPropertyElement = groupPropertiesElement.addElement("property"); groupPropertyElement.addAttribute("id", groupProperty.toString()); } } return true; } @Override public void applyColumnSettings(Element element) { super.applyColumnSettings(element); final Element groupPropertiesElement = element.element("groupProperties"); if (groupPropertiesElement != null) { final List elements = groupPropertiesElement.elements("property"); final List<MetaPropertyPath> properties = new ArrayList<>(elements.size()); for (final Object o : elements) { final MetaPropertyPath property = datasource.getMetaClass() .getPropertyPath(((Element) o).attributeValue("id")); properties.add(property); } groupBy(properties.toArray()); } } @Override protected CollectionDsWrapper createContainerDatasource(CollectionDatasource datasource, Collection<MetaPropertyPath> columns, CollectionDsListenersWrapper collectionDsListenersWrapper) { return new GroupTableDsWrapper(datasource, columns, collectionDsListenersWrapper); } @Override protected Map<Object, Object> __handleAggregationResults(AggregationContainer.Context context, Map<Object, Object> results) { if (context instanceof CubaGroupTable.GroupAggregationContext) { CubaGroupTable.GroupAggregationContext groupContext = (CubaGroupTable.GroupAggregationContext) context; for (final Map.Entry<Object, Object> entry : results.entrySet()) { final Table.Column column = columns.get(entry.getKey()); GroupAggregationCells cells; if ((cells = groupAggregationCells.get(column)) != null) { String value = cells.getValue(groupContext.getGroupId()); String cellText = getFormattedValue(column, value); entry.setValue(cellText); String groupValue = cells.getValue(groupContext.getGroupId()); if (groupValue != null) { String groupCellText = getFormattedValue(column, groupValue); entry.setValue(groupCellText); } } } return results; } else { return super.__handleAggregationResults(context, results); } } protected Object[] getNewColumnOrder(Object[] newGroupProperties) { //noinspection unchecked List<Object> allProps = new ArrayList<>(containerDatasource.getContainerPropertyIds()); List<Object> newGroupProps = Arrays.asList(newGroupProperties); allProps.removeAll(newGroupProps); allProps.addAll(0, newGroupProps); return allProps.toArray(); } protected List<Object> collectPropertiesByColumns(String... columnIds) { List<Object> properties = new ArrayList<>(); for (String columnId : columnIds) { Column column = getColumn(columnId); if (column == null) { throw new IllegalArgumentException("There is no column with the given id: " + columnId); } properties.add(column.getId()); } return properties; } protected void validateProperties(Object[] properties) { for (Object property : properties) { if (!(property instanceof MetaPropertyPath)) { throw new IllegalArgumentException( "Only MetaPropertyPaths are supported by the \"groupBy\" method."); } } } @Override public void groupBy(Object[] properties) { Preconditions.checkNotNullArgument(properties); validateProperties(properties); component.groupBy(properties); component.setColumnOrder(getNewColumnOrder(properties)); } @Override public void groupByColumns(String... columnIds) { Preconditions.checkNotNullArgument(columnIds); groupBy(collectPropertiesByColumns(columnIds).toArray()); } @Override public void ungroupByColumns(String... columnIds) { Preconditions.checkNotNullArgument(columnIds); Object[] remainingGroups = CollectionUtils .removeAll(component.getGroupProperties(), collectPropertiesByColumns(columnIds)).toArray(); groupBy(remainingGroups); } @Override public void ungroup() { groupBy(new Object[] {}); } @Override public boolean getColumnGroupAllowed(String columnId) { Column column = getColumnNN(columnId); return getColumnGroupAllowed(column); } @Override public boolean getColumnGroupAllowed(Column column) { checkNotNullArgument(column, "column must be non null"); return component.getColumnGroupAllowed(column.getId()); } @Override public void setColumnGroupAllowed(String columnId, boolean allowed) { Column column = getColumnNN(columnId); setColumnGroupAllowed(column, allowed); } @Nonnull protected Column getColumnNN(String columnId) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } return column; } @Override public void setColumnGroupAllowed(Column column, boolean allowed) { checkNotNullArgument(column, "column must be non null"); if (column.isGroupAllowed() != allowed) { column.setGroupAllowed(allowed); } component.setColumnGroupAllowed(column.getId(), allowed); } @Override public GroupCellValueFormatter<E> getGroupCellValueFormatter() { return groupCellValueFormatter; } @Override public void setGroupCellValueFormatter(GroupCellValueFormatter<E> formatter) { this.groupCellValueFormatter = formatter; } @Override public void expandAll() { component.expandAll(); } @Override public void expand(GroupInfo groupId) { component.expand(groupId); } @SuppressWarnings("unchecked") @Override public void expandPath(Entity item) { if (component.hasGroups()) { expandGroupsFor((Collection<GroupInfo>) component.rootGroups(), item.getId()); } } @SuppressWarnings("unchecked") protected void expandGroupsFor(Collection<GroupInfo> groupSlice, Object itemId) { for (GroupInfo g : groupSlice) { if (component.getGroupItemIds(g).contains(itemId)) { component.expand(g); if (component.hasChildren(g)) { expandGroupsFor((Collection<GroupInfo>) component.getChildren(g), itemId); } return; } } } @Override public void collapseAll() { component.collapseAll(); } @Override public void collapse(GroupInfo groupId) { component.collapse(groupId); } @Override public boolean isExpanded(GroupInfo groupId) { return component.isExpanded(groupId); } @Override public boolean isFixedGrouping() { return component.isFixedGrouping(); } @Override public void setFixedGrouping(boolean fixedGrouping) { component.setFixedGrouping(fixedGrouping); } @Override public boolean isShowItemsCountForGroup() { return showItemsCountForGroup; } @Override public void setShowItemsCountForGroup(boolean showItemsCountForGroup) { this.showItemsCountForGroup = showItemsCountForGroup; } @Override protected String getGeneratedCellStyle(Object itemId, Object propertyId) { if (itemId instanceof GroupInfo) { List<GroupStyleProvider> groupStyleProviders = null; for (StyleProvider styleProvider : styleProviders) { if (styleProvider instanceof GroupStyleProvider) { if (groupStyleProviders == null) { groupStyleProviders = new LinkedList<>(); } groupStyleProviders.add((GroupStyleProvider) styleProvider); } } if (groupStyleProviders != null) { String joinedStyle = null; for (GroupStyleProvider groupStyleProvider : groupStyleProviders) { String styleName = groupStyleProvider.getStyleName((GroupInfo) itemId); if (styleName != null) { if (joinedStyle == null) { joinedStyle = styleName; } else { joinedStyle += " " + styleName; } } } return joinedStyle; } } else { return super.getGeneratedCellStyle(itemId, propertyId); } return null; } @Override public Map<Object, Object> getAggregationResults(GroupInfo info) { return component.aggregate(new CubaGroupTable.GroupAggregationContext(component, info)); } protected class GroupTableDsWrapper extends SortableCollectionDsWrapper implements GroupTableContainer, AggregationContainer { protected boolean groupDatasource; protected List<Object> aggregationProperties = null; //Supports items expanding protected final Set<GroupInfo> expanded = new HashSet<>(); protected Set<GroupInfo> expandState = new HashSet<>(); //Items cache protected LinkedList<Object> cachedItemIds; protected Object first; protected Object last; public GroupTableDsWrapper(CollectionDatasource datasource, Collection<MetaPropertyPath> properties, CollectionDsListenersWrapper collectionDsListenersWrapper) { super(datasource, properties, true, collectionDsListenersWrapper); groupDatasource = datasource instanceof GroupDatasource; } @Override protected void createProperties(View view, MetaClass metaClass) { if (columns.isEmpty()) { super.createProperties(view, metaClass); } else { for (Map.Entry<Object, Column> entry : columns.entrySet()) { if (entry.getKey() instanceof MetaPropertyPath) { properties.add((MetaPropertyPath) entry.getKey()); } } } } @Override protected ItemWrapper createItemWrapper(Object item) { return new ItemWrapper(item, datasource.getMetaClass(), properties) { @Override protected PropertyWrapper createPropertyWrapper(Object item, MetaPropertyPath propertyPath) { return new TablePropertyWrapper(item, propertyPath); } }; } @Override public void groupBy(Object[] properties) { if (groupDatasource) { doGroup(properties); } } protected void doGroup(Object[] properties) { saveState(); ((GroupDatasource) datasource).groupBy(properties); restoreState(); resetCachedItems(); if (aggregationCells != null) { if (hasGroups()) { if (groupAggregationCells == null) { groupAggregationCells = new HashMap<>(); } else { groupAggregationCells.clear(); } fillGroupAggregationCells(groupAggregationCells); } else { groupAggregationCells = null; } } } protected void saveState() { //save expanding state expandState.clear(); expandState.addAll(expanded); } protected void restoreState() { collapseAll(); //restore groups expanding if (hasGroups()) { for (final GroupInfo groupInfo : expandState) { expand(groupInfo); } } expandState.clear(); } protected void fillGroupAggregationCells(Map<Table.Column, GroupAggregationCells> cells) { final Collection roots = rootGroups(); for (final Object rootGroup : roots) { __fillGroupAggregationCells(rootGroup, cells); } } protected void __fillGroupAggregationCells(Object groupId, Map<Table.Column, GroupAggregationCells> cells) { final Set<Table.Column> aggregatableColumns = aggregationCells.keySet(); for (final Column column : aggregatableColumns) { if (!columns.get(getGroupProperty(groupId)).equals(column)) { GroupAggregationCells groupCells = cells.get(column); if (groupCells == null) { groupCells = new GroupAggregationCells(); cells.put(column, groupCells); } groupCells.addCell(groupId, ""); } } if (hasChildren(groupId)) { final Collection children = getChildren(groupId); for (final Object child : children) { __fillGroupAggregationCells(child, cells); } } } @Override public Collection<?> rootGroups() { if (hasGroups()) { return ((GroupDatasource) datasource).rootGroups(); } return Collections.emptyList(); } @Override public boolean hasChildren(Object id) { return isGroup(id) && ((GroupDatasource) datasource).hasChildren((GroupInfo) id); } @Override public Collection<?> getChildren(Object id) { if (isGroup(id)) { return ((GroupDatasource) datasource).getChildren((GroupInfo) id); } return Collections.emptyList(); } @Override public Collection<?> getGroupItemIds(Object id) { if (isGroup(id)) { return ((GroupDatasource) datasource).getGroupItemIds((GroupInfo) id); } return Collections.emptyList(); } @Override public int getGroupItemsCount(Object id) { if (isGroup(id)) { return ((GroupDatasource) datasource).getGroupItemsCount((GroupInfo) id); } return 0; } @Override public boolean isGroup(Object id) { return (id instanceof GroupInfo) && ((GroupDatasource) datasource).containsGroup((GroupInfo) id); } @Override public Object getGroupProperty(Object id) { if (isGroup(id)) { return ((GroupDatasource) datasource).getGroupProperty((GroupInfo) id); } return null; } @Override public Object getGroupPropertyValue(Object id) { if (isGroup(id)) { return ((GroupDatasource) datasource).getGroupPropertyValue((GroupInfo) id); } return null; } @Override public boolean hasGroups() { return groupDatasource && ((GroupDatasource) datasource).hasGroups(); } @Override public Collection<?> getGroupProperties() { if (hasGroups()) { return ((GroupDatasource) datasource).getGroupProperties(); } return Collections.emptyList(); } @Override public void expandAll() { if (hasGroups()) { this.expanded.clear(); expand(rootGroups()); resetCachedItems(); } } protected void expand(Collection groupIds) { for (final Object groupId : groupIds) { expanded.add((GroupInfo) groupId); if (hasChildren(groupId)) { expand(getChildren(groupId)); } } } @Override public void expand(Object id) { if (isGroup(id)) { expanded.add((GroupInfo) id); resetCachedItems(); } } @Override public void collapseAll() { if (hasGroups()) { expanded.clear(); resetCachedItems(); } } @Override public void collapse(Object id) { if (isGroup(id)) { //noinspection RedundantCast expanded.remove((GroupInfo) id); resetCachedItems(); } } @Override public boolean isExpanded(Object id) { //noinspection RedundantCast return isGroup(id) && expanded.contains((GroupInfo) id); } @Override public Collection getAggregationPropertyIds() { if (aggregationProperties != null) { return Collections.unmodifiableList(aggregationProperties); } return Collections.emptyList(); } @Override public Type getContainerPropertyAggregation(Object propertyId) { throw new UnsupportedOperationException(); } @Override public void addContainerPropertyAggregation(Object propertyId, Type type) { if (aggregationProperties == null) { aggregationProperties = new LinkedList<>(); } else if (aggregationProperties.contains(propertyId)) { throw new IllegalStateException("Such aggregation property is already exists"); } aggregationProperties.add(propertyId); } @Override public void removeContainerPropertyAggregation(Object propertyId) { if (aggregationProperties != null) { aggregationProperties.remove(propertyId); if (aggregationProperties.isEmpty()) { aggregationProperties = null; } } } @SuppressWarnings("unchecked") @Override public Map<Object, Object> aggregate(Context context) { return __aggregate(this, context); } @Override public Object firstItemId() { if (hasGroups()) { return first; } return super.firstItemId(); } @Override public Object lastItemId() { if (hasGroups()) { return last; } return super.lastItemId(); } @Override public Object nextItemId(Object itemId) { if (hasGroups()) { if (itemId == null) { return null; } if (isLastId(itemId)) { return null; } int index = getCachedItemIds().indexOf(itemId); return getCachedItemIds().get(index + 1); } return super.nextItemId(itemId); } @Override public Object prevItemId(Object itemId) { if (hasGroups()) { if (itemId == null) { return null; } if (isFirstId(itemId)) { return null; } int index = getCachedItemIds().indexOf(itemId); return getCachedItemIds().get(index - 1); } return super.prevItemId(itemId); } @Override public boolean isFirstId(Object itemId) { if (hasGroups()) { return itemId != null && itemId.equals(first); } return super.isFirstId(itemId); } @Override public boolean isLastId(Object itemId) { if (hasGroups()) { return itemId != null && itemId.equals(last); } return super.isLastId(itemId); } @Override public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } @Override public Collection getItemIds() { if (hasGroups()) { return getCachedItemIds(); } else { return super.getItemIds(); } } @Override public void sort(Object[] propertyId, boolean[] ascending) { resetCachedItems(); super.sort(propertyId, ascending); } protected LinkedList getCachedItemIds() { if (cachedItemIds == null) { final LinkedList<Object> result = new LinkedList<>(); //noinspection unchecked final List<GroupInfo> roots = ((GroupDatasource) datasource).rootGroups(); for (final GroupInfo root : roots) { result.add(root); collectItemIds(root, result); } cachedItemIds = result; if (!cachedItemIds.isEmpty()) { first = cachedItemIds.peekFirst(); last = cachedItemIds.peekLast(); } } return cachedItemIds; } protected void collectItemIds(GroupInfo groupId, final List<Object> itemIds) { if (expanded.contains(groupId)) { if (((GroupDatasource) datasource).hasChildren(groupId)) { @SuppressWarnings("unchecked") final List<GroupInfo> children = ((GroupDatasource) datasource).getChildren(groupId); for (final GroupInfo child : children) { itemIds.add(child); collectItemIds(child, itemIds); } } else { itemIds.addAll(((GroupDatasource) datasource).getGroupItemIds(groupId)); } } } protected void resetCachedItems() { cachedItemIds = null; first = null; last = null; } @Override public int size() { if (hasGroups()) { return getItemIds().size(); } return super.size(); } @Override protected Datasource.StateChangeListener createStateChangeListener() { return new ContainerDatasourceStateChangeListener() { @Override public void stateChanged(Datasource.StateChangeEvent e) { rerender = false; Collection groupProperties = component.getGroupProperties(); component.groupBy(groupProperties.toArray()); super.stateChanged(e); rerender = true; } }; } @Override protected CollectionDatasource.CollectionChangeListener createCollectionChangeListener() { return new ContainerDatasourceCollectionChangeListener() { @Override public void collectionChanged(CollectionDatasource.CollectionChangeEvent e) { Collection groupProperties = component.getGroupProperties(); component.groupBy(groupProperties.toArray()); super.collectionChanged(e); } }; } @Override public void resetSortOrder() { if (datasource instanceof CollectionDatasource.Sortable) { ((CollectionDatasource.Sortable) datasource).resetSortOrder(); } } } protected class AggregatableGroupPropertyValueFormatter extends DefaultGroupPropertyValueFormatter { @Override public String format(Object groupId, @Nullable Object value) { String formattedValue = super.format(groupId, value); if (groupCellValueFormatter != null) { List<Entity> groupItems = component.getGroupItemIds(groupId).stream() .map(itemId -> ((ItemWrapper) component.getItem(itemId)).getItem()) .collect(Collectors.toList()); GroupCellContext<E> context = new GroupCellContext<>((GroupInfo) groupId, value, formattedValue, (List<E>) groupItems); return groupCellValueFormatter.format(context); } if (showItemsCountForGroup) { int count = WebGroupTable.this.component.getGroupItemsCount(groupId); return String.format("%s (%d)", formattedValue == null ? "" : formattedValue, count); } else { return formattedValue == null ? "" : formattedValue; } } } protected class DefaultGroupPropertyValueFormatter implements CubaGroupTable.GroupPropertyValueFormatter { @SuppressWarnings("unchecked") @Override public String format(Object groupId, @Nullable Object value) { if (value == null) { return ""; } final MetaPropertyPath propertyPath = ((GroupInfo<MetaPropertyPath>) groupId).getProperty(); final Table.Column column = columns.get(propertyPath); if (column != null && column.getXmlDescriptor() != null) { String captionProperty = column.getXmlDescriptor().attributeValue("captionProperty"); if (column.getFormatter() != null) { return column.getFormatter().format(value); } else if (StringUtils.isNotEmpty(captionProperty)) { Collection<?> children = component.getGroupItemIds(groupId); if (children.isEmpty()) { return null; } Object itemId = children.iterator().next(); Instance item = ((ItemWrapper) component.getItem(itemId)).getItem(); final Object captionValue = item.getValueEx(captionProperty); return captionValue != null ? String.valueOf(captionValue) : null; } } MetadataTools metadataTools = AppBeans.get(MetadataTools.NAME); return metadataTools.format(value, propertyPath.getMetaProperty()); } } protected static class GroupAggregationCells { protected Map<Object, String> cells = new HashMap<>(); public void addCell(Object groupId, String value) { cells.put(groupId, value); } public String getValue(Object groupId) { return cells.get(groupId); } } @Override public void addColumn(Column column) { super.addColumn(column); setColumnGroupAllowed(column, column.isGroupAllowed()); } @Override protected CollectionDsListenersWrapper createCollectionDsListenersWrapper() { return new GroupTableCollectionDsListenersWrapper(); } public class GroupTableCollectionDsListenersWrapper extends TableCollectionDsListenersWrapper { @Override protected void handleAggregation() { super.handleAggregation(); if (isAggregatable() && aggregationCells != null) { if (datasource instanceof GroupDatasource) { GroupDatasource groupDs = ((GroupDatasource) datasource); @SuppressWarnings("unchecked") Collection<GroupInfo> roots = groupDs.rootGroups(); for (final GroupInfo root : roots) { component.aggregate(new CubaGroupTable.GroupAggregationContext(component, root)); } } } } } }