blob: 8ced9fe04e4dd4328b086a6fdbf2e6829a7705fe [file] [log] [blame]
/**
*
* Copyright (c) 2011, 2016 - Loetz GmbH&Co.KG (69115 Heidelberg, Germany)
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Christophe Loetz (Loetz GmbH&Co.KG) - initial implementation
*/
package org.eclipse.osbp.infogrid.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ClassUtils;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.osbp.ecview.core.common.model.core.YCompare;
import org.eclipse.osbp.ecview.core.common.model.core.YConverter;
import org.eclipse.osbp.ecview.core.common.model.core.YField;
import org.eclipse.osbp.ecview.extension.grid.CxGrid;
import org.eclipse.osbp.ecview.extension.grid.CxGridColumn;
import org.eclipse.osbp.ecview.extension.grid.CxGridFactory;
import org.eclipse.osbp.ecview.extension.grid.CxGridGroupedCell;
import org.eclipse.osbp.ecview.extension.grid.CxGridHeaderRow;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridBlobImageRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridButtonRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridDateRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridImageRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridIndicatorRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridNestedConverter;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridNumberRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridPriceRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridProgressBarRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridQuantityRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridRenderer;
import org.eclipse.osbp.ecview.extension.grid.renderer.CxGridRendererFactory;
import org.eclipse.osbp.ecview.extension.model.converter.YConverterFactory;
import org.eclipse.osbp.ecview.extension.model.converter.YNumericToResourceConfig;
import org.eclipse.osbp.ecview.extension.model.converter.YNumericToResourceConverter;
import org.eclipse.osbp.ecview.extension.model.converter.YStringToResourceConfig;
import org.eclipse.osbp.ecview.extension.model.converter.YStringToResourceConverter;
import org.eclipse.osbp.infogrid.model.gridsource.CxGridProperty;
import org.eclipse.osbp.infogrid.model.gridsource.CxGridSource;
import org.eclipse.osbp.infogrid.model.gridsource.CxGridSourceEQFilter;
import org.eclipse.osbp.infogrid.model.gridsource.CxGridSourceInput;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridCompare;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropBlobImageStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropButtonStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropDateStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropImageStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropIndicatorStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropNumberStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropPriceStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropProgressbarStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridPropQuantityStyle;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridStyleConfig;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridStyleConfigNumericToResource;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridStyleConfigStringToResource;
import org.eclipse.osbp.infogrid.model.gridsource.style.CxGridStylePackage;
import org.eclipse.osbp.runtime.common.types.IBundleSpace;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SingleECViewGridFactory implements IECViewGridFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(SingleECViewGridFactory.class);
private CxGridFactory gridFactory = CxGridFactory.eINSTANCE;
// make extendable by services later
private IECViewGridEditorFieldFactory editorFieldFactory = new ECViewGridEditorFieldFactory();
public SingleECViewGridFactory() {
}
@Override
public CxGrid createGrid(Class<?> inputType, CxGridSource gridSource) {
BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceReference<IBundleSpace> bsRef = bc.getServiceReference(IBundleSpace.class);
if (bsRef == null) {
LOGGER.error("No BundleSpace available.");
return gridFactory.createCxGrid();
}
IBundleSpace bundleSpace = bc.getService(bsRef);
Class<?> rootType = null;
try {
rootType = bundleSpace.forName(gridSource.getRootTypeFQN());
} catch (ClassNotFoundException e) {
LOGGER.error("{}", e);
}
bc.ungetService(bsRef);
CxGrid grid = gridFactory.createCxGrid();
grid.setId(GRID_ID);
grid.getProperties().put(GRIDSOURCE_ID, gridSource.getId());
grid.setEditorEnabled(isEditable(gridSource));
grid.setHeaderVisible(true);
grid.setCustomFilters(false);
grid.setFilteringVisible(true);
grid.setUseBeanService(!gridSource.getTags().contains(IECViewGridSourceDescriptor.PROP_MANUAL_BEANS));
grid.setColumnReorderingAllowed(true);
if (rootType != null) {
grid.setType(rootType);
grid.setTypeQualifiedName(rootType.getName());
List<Info> temp = new ArrayList<>(gridSource.getProperties().size());
// create the basic infos. In further steps we will group them and
// calculate editable states.
//
for (CxGridProperty prop : gridSource.getProperties()) {
Info info = new Info();
info.prop = prop;
temp.add(info);
if (GridUtil.isNested(rootType, prop)) {
info.group = GridUtil.getGroup(rootType, prop);
info.firstNestedDtoType = GridUtil.getDtoType(rootType, prop);
info.nested = true;
boolean bindsRawDto = GridUtil.isBindsRawDto(rootType, prop);
info.rawDtoRef = bindsRawDto;
if (bindsRawDto) {
info.fullDotPath = prop.getDotPath() + "." + GridUtil.getDomainKeyName(rootType, prop);
info.prop.setDotPath(info.fullDotPath);
info.domainkey = true;
// ATTENTION: Here we need to use the dto type, since
// this type is bound to the grid column
info.type = GridUtil.getPropertyType(rootType, prop);
} else {
info.fullDotPath = prop.getDotPath();
info.domainkey = GridUtil.isBindsDomainKey(rootType, prop);
info.type = GridUtil.getPropertyType(rootType, prop);
}
} else {
info.group = "";
info.fullDotPath = prop.getDotPath();
info.type = GridUtil.getPropertyType(rootType, prop);
}
}
// filter double entries and create a map for groupings
//
Map<String, List<Info>> infoGroup = new HashMap<>();
List<Info> infos = new ArrayList<>(gridSource.getProperties().size());
Set<String> dotPath = new HashSet<>();
for (Info info : temp) {
if (dotPath.contains(info.fullDotPath)) {
continue;
}
if (!infoGroup.containsKey(info.group)) {
infoGroup.put(info.group, new ArrayList<>());
}
List<Info> list = infoGroup.get(info.group);
list.add(info);
dotPath.add(info.fullDotPath);
infos.add(info);
}
// check which info is editable. Only one per group is editable.
// Preferred the domainkey property
for (Map.Entry<String, List<Info>> entry : infoGroup.entrySet()) {
boolean domainkeyFound = false;
for (Info info : entry.getValue()) {
if (info.group.equals("")) {
// the root group is always editable according its
// property
info.editable = info.prop.isEditable();
continue;
}
if (info.domainkey) {
info.editable = true;
domainkeyFound = true;
break;
}
}
if (!domainkeyFound) {
entry.getValue().get(0).editable = true;
}
}
// create a header row for the grouped columns
//
temp = new ArrayList<>(infos);
infos.clear();
CxGridHeaderRow groupingRow = CxGridFactory.eINSTANCE.createCxGridHeaderRow();
grid.getHeaders().add(groupingRow);
for (String group : infoGroup.keySet()) {
CxGridGroupedCell groupedCell = CxGridFactory.eINSTANCE.createCxGridGroupedCell();
groupedCell.setId(group + "Group");
groupedCell.setName(group + "Group");
groupedCell.setLabelI18nKey(group);
groupingRow.getGroupings().add(groupedCell);
// find grouped infos
List<Info> groupedCells = temp.stream().filter(info -> info.group.equals(group))
.collect(Collectors.toList());
// and apply the cxGridGroup
groupedCells.stream().forEach(info -> info.cxGridGroup = groupedCell);
infos.addAll(groupedCells);
}
for (Info info : infos) {
CxGridProperty prop = info.prop;
CxGridColumn column = gridFactory.createCxGridColumn();
column.setType(info.type);
column.setTypeQualifiedName(info.type.getName());
if (info.cxGridGroup != null) {
info.cxGridGroup.getGroupables().add(column);
}
if (info.editable) {
// we create the column based on the dto and use the
// converter to convert to the target property inside the
// dto
column.setPropertyPath(GridUtil.getDtoBindingPath(rootType, prop));
} else {
// otherwise we create the column with the fully dto path
column.setPropertyPath(prop.getDotPath());
}
column.setPropertyId(column.getPropertyPath());
column.setLabelI18nKey(prop.getDotPath());
Class<?> propertyType = BeanProperties.value(rootType, prop.getDotPath()).getPropertyDescriptor()
.getPropertyType();
// editable
if (info.editable) {
YField yEditorField = getEditorField(prop, propertyType, rootType);
column.setEditable(yEditorField != null);
column.setEditorField(yEditorField);
if (info.nested) {
// only one nested property is editable. So it needs to
// edit the dto by combobox and requires a different
// filterPropertyPath.
// The bindings property path points directly to the dto
column.setEditsDto(true);
column.setFilterPropertyPathForEditsDto(info.fullDotPath);
}
} else {
column.setEditable(false);
}
Pair pair = createRendererPair(rootType, prop, propertyType, info);
column.setRenderer(pair.renderer);
column.setConverter(pair.converter);
// column.setMaxWidthPixels(50);
grid.getColumns().add(column);
}
}
for (CxGridSourceInput input : gridSource.getInputs()) {
if (inputType.getName().equals(input.getInputTypeFQN())) {
for (CxGridSourceEQFilter filter : input.getFilters()) {
// take the filter value from the input
filter.getInputTypePropertyPath();
}
}
}
return grid;
}
/**
* Returns the editor field for the given propertyType.
*
* @param prop
* @param propertyType
* @param rootType
* @return
*/
protected YField getEditorField(CxGridProperty prop, Class<?> propertyType, Class<?> rootType) {
return editorFieldFactory.createEditorField(prop, propertyType, rootType);
}
protected boolean isEditable(CxGridSource gridSource) {
for (CxGridProperty prop : gridSource.getProperties()) {
if (prop.isEditable()) {
return true;
}
}
return false;
}
/**
* Creates a renderer for the given property.
*
* @param prop
* @param propertyType
* @param editable
* @return
*/
protected Pair createRendererPair(Class<?> rootType, CxGridProperty prop, Class<?> propertyType, Info info) {
Pair pair = new Pair();
CxGridRendererFactory factory = CxGridRendererFactory.eINSTANCE;
if (prop.getStyle() == null) {
return createBadRenderer();
}
EClass styleClass = prop.getStyle().eClass();
switch (styleClass.getClassifierID()) {
case CxGridStylePackage.CX_GRID_PROP_BOOLEAN_STYLE:
pair.renderer = factory.createCxGridBooleanRenderer();
break;
case CxGridStylePackage.CX_GRID_PROP_DATE_STYLE: {
CxGridPropDateStyle style = (CxGridPropDateStyle) prop.getStyle();
CxGridDateRenderer renderer = factory.createCxGridDateRenderer();
renderer.setDateFormat(style.getDateFormat());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_NUMBER_STYLE: {
if (!isNumeric(propertyType)) {
return createBadRenderer();
}
CxGridPropNumberStyle style = (CxGridPropNumberStyle) prop.getStyle();
CxGridNumberRenderer renderer = factory.createCxGridNumberRenderer();
renderer.setNumberFormat(style.getNumberFormat());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_BUTTON_STYLE: {
CxGridPropButtonStyle style = (CxGridPropButtonStyle) prop.getStyle();
CxGridButtonRenderer renderer = factory.createCxGridButtonRenderer();
renderer.setEventTopic(style.getEventTopic());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_IMAGE_STYLE: {
CxGridPropImageStyle style = (CxGridPropImageStyle) prop.getStyle();
YConverter converter = toConverter(style.getConfigs());
CxGridImageRenderer renderer = factory.createCxGridImageRenderer();
renderer.setEventTopic(style.getEventTopic());
pair.renderer = renderer;
pair.converter = converter;
}
break;
case CxGridStylePackage.CX_GRID_PROP_BLOB_IMAGE_STYLE: {
YConverter converter = YConverterFactory.eINSTANCE.createYStringToByteArrayConverter();
CxGridBlobImageRenderer renderer = factory.createCxGridBlobImageRenderer();
pair.renderer = renderer;
pair.converter = converter;
}
break;
case CxGridStylePackage.CX_GRID_PROP_HTML_STYLE:
pair.renderer = factory.createCxGridHtmlRenderer();
break;
case CxGridStylePackage.CX_GRID_PROP_PROGRESSBAR_STYLE: {
CxGridPropProgressbarStyle style = (CxGridPropProgressbarStyle) prop.getStyle();
CxGridProgressBarRenderer renderer = factory.createCxGridProgressBarRenderer();
renderer.setMaxValue(style.getMaxValue());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_INDICATOR_STYLE: {
CxGridPropIndicatorStyle style = (CxGridPropIndicatorStyle) prop.getStyle();
CxGridIndicatorRenderer renderer = factory.createCxGridIndicatorRenderer();
renderer.setGreenStarts(style.getGreenStarts());
renderer.setRedEnds(style.getRedEnds());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_SPARKLINE_STYLE:
pair.renderer = factory.createCxGridHtmlRenderer();
break;
case CxGridStylePackage.CX_GRID_PROP_TEXT_STYLE:
pair.renderer = factory.createCxGridHtmlRenderer();
break;
case CxGridStylePackage.CX_GRID_PROP_QUANTITY_STYLE: {
CxGridPropQuantityStyle style = (CxGridPropQuantityStyle) prop.getStyle();
CxGridQuantityRenderer renderer = factory.createCxGridQuantityRenderer();
renderer.setHtmlPattern(style.getHtmlPattern());
renderer.setNullRepresentation("");
renderer.setNumberFormat(style.getValueNumberFormat());
renderer.setUomPropertyPath(style.getUomPropertyDotPath());
renderer.setValuePropertyPath(style.getValuePropertyDotPath());
pair.renderer = renderer;
}
break;
case CxGridStylePackage.CX_GRID_PROP_PRICE_STYLE: {
CxGridPropPriceStyle style = (CxGridPropPriceStyle) prop.getStyle();
CxGridPriceRenderer renderer = factory.createCxGridPriceRenderer();
renderer.setHtmlPattern(style.getHtmlPattern());
renderer.setNullRepresentation("");
renderer.setNumberFormat(style.getValueNumberFormat());
renderer.setCurrencyPropertyPath(style.getCurrencyPropertyDotPath());
renderer.setValuePropertyPath(style.getValuePropertyDotPath());
pair.renderer = renderer;
}
break;
}
if (info.nested && info.editable) {
CxGridNestedConverter nestedConverter = CxGridRendererFactory.eINSTANCE.createCxGridNestedConverter();
Class<?> dtoType = GridUtil.getDtoType(rootType, prop);
nestedConverter.setBaseType(dtoType);
nestedConverter.setBaseTypeQualifiedName(dtoType.getName());
if (propertyType.isPrimitive()) {
propertyType = ClassUtils.primitiveToWrapper(propertyType);
}
nestedConverter.setNestedType(propertyType);
nestedConverter.setNestedTypeQualifiedName(propertyType.getName());
// we create the column based on the dto and use the
// converter to point to the target property inside the dto
String nestedConverterTailPath = GridUtil.getNestedConverterTailDotPath(rootType, prop);
nestedConverter.setNestedDotPath(nestedConverterTailPath);
// the nestedConverter needs to use the nested type converter to
// convert to the presentation. Eg, the nestedConverter extracts
// birthdate from Person and the nestedTypeConverter does pretty
// formatting of date
nestedConverter.setNestedTypeConverter(pair.converter);
pair.converter = nestedConverter;
}
return pair;
}
protected boolean isNumeric(Class<?> propertyType) {
boolean result = Number.class.isAssignableFrom(propertyType);
if (!result) {
if (propertyType.isPrimitive() && propertyType != Boolean.TYPE && propertyType != Void.TYPE) {
result = true;
}
}
return result;
}
protected YConverter toConverter(EList<CxGridStyleConfig> configs) {
if (configs.isEmpty()) {
return null;
}
CxGridStyleConfig cxConfig = configs.get(0);
if (cxConfig instanceof CxGridStyleConfigStringToResource) {
List<YStringToResourceConfig> newConfigs = toStringToResourceConfig(configs);
YStringToResourceConverter converter = YConverterFactory.eINSTANCE.createYStringToResourceConverter();
converter.getConfigs().addAll(newConfigs);
return converter;
} else if (cxConfig instanceof CxGridStyleConfigNumericToResource) {
List<YNumericToResourceConfig> newConfigs = toNumberToResourceConfig(configs);
YNumericToResourceConverter converter = YConverterFactory.eINSTANCE.createYNumericToResourceConverter();
converter.getConfigs().addAll(newConfigs);
return converter;
}
return null;
}
private List<YNumericToResourceConfig> toNumberToResourceConfig(EList<CxGridStyleConfig> configs) {
List<YNumericToResourceConfig> result = new ArrayList<>(configs.size());
for (CxGridStyleConfig config : configs) {
if (!(config instanceof CxGridStyleConfigNumericToResource)) {
LOGGER.warn("you mixed up different style configs!");
continue;
}
CxGridStyleConfigNumericToResource givenConfig = (CxGridStyleConfigNumericToResource) config;
YNumericToResourceConfig newConfig = YConverterFactory.eINSTANCE.createYNumericToResourceConfig();
newConfig.setResourceThemePath(givenConfig.getResourceThemePath());
newConfig.setValue(givenConfig.getValue());
newConfig.setCompare(toGridCompare(givenConfig.getCompare()));
result.add(newConfig);
}
return result;
}
private YCompare toGridCompare(CxGridCompare compare) {
switch (compare) {
case EQUAL:
return YCompare.EQUAL;
case GREATER_EQUAL:
return YCompare.GREATER_EQUAL;
case GREATER_THAN:
return YCompare.GREATER_THAN;
case LOWER_EQUAL:
return YCompare.LOWER_EQUAL;
case LOWER_THAN:
return YCompare.LOWER_THAN;
case NOT_EQUAL:
return YCompare.NOT_EQUAL;
}
return null;
}
private List<YStringToResourceConfig> toStringToResourceConfig(EList<CxGridStyleConfig> configs) {
List<YStringToResourceConfig> result = new ArrayList<>(configs.size());
for (CxGridStyleConfig config : configs) {
if (!(config instanceof CxGridStyleConfigStringToResource)) {
LOGGER.warn("you mixed up different style configs!");
continue;
}
CxGridStyleConfigStringToResource givenConfig = (CxGridStyleConfigStringToResource) config;
YStringToResourceConfig newConfig = YConverterFactory.eINSTANCE.createYStringToResourceConfig();
newConfig.setResourceThemePath(givenConfig.getResourceThemePath());
newConfig.setValue(givenConfig.getValue());
newConfig.setCompare(toGridCompare(givenConfig.getCompare()));
result.add(newConfig);
}
return result;
}
protected Pair createBadRenderer() {
Pair pair = new Pair();
pair.renderer = CxGridRendererFactory.eINSTANCE.createCxGridTextRenderer();
return pair;
}
protected static class Pair {
public CxGridRenderer renderer;
public YConverter converter;
}
protected static class Info {
public CxGridGroupedCell cxGridGroup;
CxGridProperty prop;
// true, if the column is editable
boolean editable;
// the name of the first bound dto
String group;
// the dot path extended by the domainkey if rawDtoRef is true
String fullDotPath;
// true if nested or rawDtoRef
boolean nested;
// if true, then the prop binds to the domain key
boolean domainkey;
// if true, we need to add the domainkey since the property points to a
// raw dto. Eg. person.country -> so we make it to
// person.country.isoCode
boolean rawDtoRef;
// the first dtoType in the binding path used for groupings
Class<?> firstNestedDtoType;
// the type of the bound property
Class<?> type;
}
}