1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.framework.databinding;
35
36 import static info.magnolia.util.Functions.defaultCombiner;
37 import static java.util.function.Function.identity;
38 import static java.util.stream.Collectors.toMap;
39
40 import info.magnolia.objectfactory.ComponentProvider;
41 import info.magnolia.ui.framework.databinding.converter.ItemToLinkConverterResolverStrategy;
42 import info.magnolia.ui.framework.databinding.converter.MultiItemToLinkConverter;
43 import info.magnolia.ui.field.FieldDefinition;
44 import info.magnolia.ui.field.TextFieldDefinition;
45 import info.magnolia.ui.field.WithDatasource;
46 import info.magnolia.ui.framework.databinding.validator.FieldValidatorDefinition;
47 import info.magnolia.ui.framework.databinding.validator.FieldValidatorFactory;
48
49 import java.util.Collection;
50 import java.util.Map;
51 import java.util.Optional;
52
53 import org.apache.commons.lang3.ArrayUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 import com.vaadin.data.Binder;
58 import com.vaadin.data.Converter;
59 import com.vaadin.data.ErrorMessageProvider;
60 import com.vaadin.data.HasValue;
61 import com.vaadin.data.PropertySet;
62 import com.vaadin.data.Validator;
63 import com.vaadin.ui.MultiSelect;
64
65
66
67
68
69 public class ConfiguredBinder<T> extends Binder<T> {
70
71 private static final Logger log = LoggerFactory.getLogger(ConfiguredBinder.class);
72 private final Map<String, FieldDefinition> fieldDefinitions;
73 private final ComponentProvider componentProvider;
74 private ItemToLinkConverterResolverStrategy converterResolverStrategy;
75
76 public ConfiguredBinder(Collection<? extends FieldDefinition> fieldDefinitions, ComponentProvider componentProvider, PropertySet<T> propertySet) {
77 super(propertySet);
78 this.fieldDefinitions = fieldDefinitions.stream().collect(toMap(FieldDefinition::getName, identity()));
79 this.componentProvider = componentProvider;
80 }
81
82
83
84
85
86
87 public ConfiguredBinder(Collection<? extends FieldDefinition> fieldDefinitions, ComponentProvider componentProvider, PropertySet<T> propertySet, ItemToLinkConverterResolverStrategy converterResolverStrategy) {
88 this(fieldDefinitions, componentProvider, propertySet);
89 this.converterResolverStrategy = converterResolverStrategy;
90 }
91
92 @Override
93 public <FIELDVALUE> Binding<T, FIELDVALUE> bind(HasValue<FIELDVALUE> field, String propertyName) {
94 final BindingBuilder<T, FIELDVALUE> binding = forField(field);
95 final BindingBuilder<T, FIELDVALUE> bindingBuilder = Optional.ofNullable(fieldDefinitions.get(propertyName))
96 .map(fieldDefinition -> this.applyConfiguration(binding, (FieldDefinition<FIELDVALUE>) fieldDefinition))
97 .orElse(binding);
98 return bindingBuilder.bind(propertyName);
99 }
100
101 public <PT> BindingBuilder<T, PT> applyConfiguration(BindingBuilder<T, PT> bindingBuilder, FieldDefinition<PT> definition, Object... parameters) {
102 if (definition.isRequired()) {
103 bindingBuilder.asRequired(definition.getRequiredErrorMessage());
104 }
105
106 bindingBuilder =
107 definition.getValidators().stream()
108 .map(this::newValidator)
109 .reduce(bindingBuilder, BindingBuilder::withValidator, defaultCombiner());
110
111 final Converter converter = newConverter(definition, bindingBuilder.getField(), parameters);
112 if (converter != null) {
113 bindingBuilder = bindingBuilder.withConverter(converter);
114 final Class<? extends FieldDefinition> definitionType = definition.getClass();
115 bindingBuilder = applyTextFieldNullValueRepresentationWorkaround(bindingBuilder, definitionType);
116 }
117
118 final PT fieldValue = (PT) bindingBuilder.getField().getValue();
119 if (converter == null && fieldValue != null && !definition.getType().isInstance(fieldValue)) {
120 log.warn(String.format("Type mismatch for field named [%s]. Configured type is [%s] but bound component value type is [%s] and no suitable converter has been found. Please check your configuration.",
121 definition.getName(), definition.getType(), fieldValue.getClass()));
122 }
123
124 Optional.ofNullable(definition.getDefaultValue()).ifPresent(bindingBuilder::withNullRepresentation);
125
126 return bindingBuilder;
127 }
128
129 private <PT> Converter<?, PT> newConverter(FieldDefinition<PT> definition, HasValue field, Object... parameters) {
130 final Optional<Converter> optionalConverter = Optional.ofNullable(definition.getConverterClass())
131 .map(converterClass -> componentProvider.newInstance(converterClass,
132 ArrayUtils.addAll(parameters, definition.getConversionErrorMessage(), -1L, (ErrorMessageProvider) context -> definition.getConversionErrorMessage())));
133
134
135 return optionalConverter.orElse(WithDatasource.from(definition)
136 .map(def -> resolveConverterFromDataSource(def.getDatasource(), field))
137 .orElse(null));
138 }
139
140 private <PT> Converter<?, PT> resolveConverterFromDataSource(Object datasource, HasValue field) {
141 final Optional<Converter<?, PT>> itemToLinkConverter = Optional.ofNullable(datasource)
142 .flatMap(ds -> converterResolverStrategy.resolveFromDatasource(ds));
143
144 if (field instanceof MultiSelect) {
145 return itemToLinkConverter.map(c -> new MultiItemToLinkConverter(c)).orElse(null);
146 }
147 return itemToLinkConverter.orElse(null);
148 }
149
150 private <VT> Validator<VT> newValidator(FieldValidatorDefinition definition, Object... parameters) {
151 final Class<? extends FieldValidatorFactory> factoryClass = definition.getFactoryClass();
152 final FieldValidatorFactory fieldValidatorFactory = componentProvider.newInstance(factoryClass, ArrayUtils.add(parameters, definition));
153 return fieldValidatorFactory.createValidator();
154 }
155
156 private <PT> BindingBuilder<T, PT> applyTextFieldNullValueRepresentationWorkaround(BindingBuilder<T, PT> bindingBuilder, Class<? extends FieldDefinition> definitionType) {
157
158 if (TextFieldDefinition.class.isAssignableFrom(definitionType)) {
159 bindingBuilder = (BindingBuilder<T, PT>) ((BindingBuilder<T, String>) bindingBuilder).withNullRepresentation("");
160 }
161 return bindingBuilder;
162 }
163 }