View Javadoc
1   /**
2    * This file Copyright (c) 2012-2015 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.form.field.factory;
35  
36  import info.magnolia.cms.i18n.I18nContentSupport;
37  import info.magnolia.objectfactory.ComponentProvider;
38  import info.magnolia.ui.api.view.View;
39  import info.magnolia.ui.form.AbstractFormItem;
40  import info.magnolia.ui.form.field.definition.FieldDefinition;
41  import info.magnolia.ui.form.field.definition.TextFieldDefinition;
42  import info.magnolia.ui.form.field.transformer.TransformedProperty;
43  import info.magnolia.ui.form.field.transformer.Transformer;
44  import info.magnolia.ui.form.field.transformer.UndefinedPropertyType;
45  import info.magnolia.ui.form.field.transformer.basic.BasicTransformer;
46  import info.magnolia.ui.form.validator.definition.FieldValidatorDefinition;
47  import info.magnolia.ui.form.validator.factory.FieldValidatorFactory;
48  import info.magnolia.ui.form.validator.registry.FieldValidatorFactoryFactory;
49  import info.magnolia.ui.vaadin.integration.ItemAdapter;
50  import info.magnolia.ui.vaadin.integration.jcr.DefaultPropertyUtil;
51  
52  import org.apache.commons.lang3.StringUtils;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import com.vaadin.data.Item;
57  import com.vaadin.data.Property;
58  import com.vaadin.data.util.converter.Converter;
59  import com.vaadin.server.Sizeable.Unit;
60  import com.vaadin.ui.AbstractField;
61  import com.vaadin.ui.Component;
62  import com.vaadin.ui.CssLayout;
63  import com.vaadin.ui.Field;
64  import com.vaadin.ui.Label;
65  
66  /**
67   * Abstract FieldFactory implementations. This class handle all common attributes defined in {@link FieldDefinition} and binds Vaadin {@link Field} instances created
68   * by subclasses to the {@link Property} they will be reading and writing to.
69   *
70   * @param <D> definition type
71   * @param <T> field value type
72   */
73  public abstract class AbstractFieldFactory<D extends FieldDefinition, T> extends AbstractFormItem implements FieldFactory {
74      private static final Logger log = LoggerFactory.getLogger(AbstractFieldFactory.class);
75      protected Item item;
76      protected Field<T> field;
77      protected D definition;
78      private FieldValidatorFactoryFactory fieldValidatorFactoryFactory;
79      private ComponentProvider componentProvider;
80  
81      public AbstractFieldFactory(D definition, Item relatedFieldItem) {
82          this.definition = definition;
83          this.item = relatedFieldItem;
84      }
85  
86      @Override
87      public void setFieldValidatorFactoryFactory(FieldValidatorFactoryFactory fieldValidatorFactoryFactory) {
88          this.fieldValidatorFactoryFactory = fieldValidatorFactoryFactory;
89      }
90  
91      /**
92       * @deprecated This is deprecated since 5.3.4; {@link i18nContentSupport} was never used within any {@link FieldFactory}, rightfully so.
93       * If any, {@link info.magnolia.ui.api.i18n.I18NAuthoringSupport I18NAuthoringSupport} is the one that should be used.
94       */
95      @Override
96      @Deprecated
97      public void setI18nContentSupport(I18nContentSupport i18nContentSupport) {
98      }
99  
100     @Override
101     public Field<T> createField() {
102         if (field == null) {
103             // Create the Vaadin field
104             this.field = createFieldComponent();
105             if (field instanceof AbstractField && definition.getConverterClass() != null) {
106                 Converter<?, ?> converter = initializeConverter(definition.getConverterClass());
107                 ((AbstractField) field).setConverter(converter);
108             }
109 
110             Property<?> property = initializeProperty();
111             // Set the created property with the default value as field Property datasource.
112             setPropertyDataSourceAndDefaultValue(property);
113 
114             if (StringUtils.isNotBlank(definition.getStyleName())) {
115                 this.field.addStyleName(definition.getStyleName());
116             }
117 
118             field.setWidth(100, Unit.PERCENTAGE);
119 
120             // Set label and required marker
121             if (StringUtils.isNotBlank(getFieldDefinition().getLabel())) {
122                 this.field.setCaption(getFieldDefinition().getLabel() + (getFieldDefinition().isRequired() ? "<span class=\"requiredfield\">*</span>" : ""));
123             }
124 
125             setConstraints();
126 
127         }
128         return this.field;
129     }
130 
131     /**
132      * Set the DataSource of the current field.<br>
133      * Set the default value if : <br>
134      * - the item is an instance of {@link ItemAdapter} and this is a new Item (Not yet stored in the related datasource).<br>
135      * - the item is not an instance of {@link ItemAdapter}.<br>
136      * In this case, the Item is a custom implementation of {@link Item} and we have no possibility to define if it is or not a new Item.<br>
137      */
138     public void setPropertyDataSourceAndDefaultValue(Property<?> property) {
139         this.field.setPropertyDataSource(property);
140 
141         if ((item instanceof ItemAdapter && ((ItemAdapter) item).isNew() && property.getValue() == null) || (!(item instanceof ItemAdapter) && property.getValue() == null)) {
142             setPropertyDataSourceDefaultValue(property);
143         }
144     }
145 
146     /**
147      * Set the Field default value is required.
148      */
149     protected void setPropertyDataSourceDefaultValue(Property property) {
150         Object defaultValue = createDefaultValue(property);
151         if (defaultValue != null && !definition.isReadOnly()) {
152             if (defaultValue.getClass().isAssignableFrom(property.getType())) {
153                 property.setValue(defaultValue);
154             } else {
155                 log.warn("Default value {} is not assignable to the field of type {}.", defaultValue, field.getPropertyDataSource().getType().getName());
156             }
157         }
158     }
159 
160     /**
161      * Create a typed default value.
162      */
163     protected Object createDefaultValue(Property<?> property) {
164         String defaultValue = definition.getDefaultValue();
165         if (StringUtils.isNotBlank(defaultValue)) {
166             return DefaultPropertyUtil.createTypedValue(property.getType(), defaultValue);
167         }
168         return null;
169     }
170 
171     @Override
172     public D getFieldDefinition() {
173         return this.definition;
174     }
175 
176     /**
177      * Implemented by subclasses to create and initialize the Vaadin Field instance to use.
178      */
179     protected abstract Field<T> createFieldComponent();
180 
181     @Override
182     public View getView() {
183         final CssLayout fieldView = new CssLayout();
184         fieldView.setStyleName("field-view");
185 
186         Label label = new Label();
187         label.setSizeUndefined();
188         label.setCaption(getFieldDefinition().getLabel());
189 
190         if (getFieldDefinition().getClass().isAssignableFrom(TextFieldDefinition.class)) {
191             final TextFieldDefinition textFieldDefinition = (TextFieldDefinition) getFieldDefinition();
192             if (textFieldDefinition.getRows() > 0) {
193                 label.addStyleName("textarea");
194             }
195         }
196         if (definition.getConverterClass() != null) {
197             Converter converter = initializeConverter(definition.getConverterClass());
198             label.setConverter(converter);
199         }
200 
201         Property<?> property = initializeProperty();
202 
203         label.setPropertyDataSource(property);
204 
205         fieldView.addComponent(label);
206 
207         return new View() {
208             @Override
209             public Component asVaadinComponent() {
210                 return fieldView;
211             }
212         };
213     }
214 
215     /**
216      * Initialize the property used as field's Datasource.<br>
217      * If no {@link Transformer} is configure to the field definition, use the default {@link BasicTransformer} <br>
218      */
219     @SuppressWarnings("unchecked")
220     private Property<?> initializeProperty() {
221         Class<? extends Transformer<?>> transformerClass = definition.getTransformerClass();
222 
223         if (transformerClass == null) {
224             // TODO explain why down cast
225             transformerClass = (Class<? extends Transformer<?>>) (Object) BasicTransformer.class;
226         }
227         Transformer<?> transformer = initializeTransformer(transformerClass);
228 
229         return new TransformedProperty(transformer);
230     }
231 
232     /**
233      * Exposed method used by field's factory to initialize the property {@link Transformer}.<br>
234      * This allows to add additional constructor parameter if needed.<br>
235      */
236     protected Transformer<?> initializeTransformer(Class<? extends Transformer<?>> transformerClass) {
237         return this.componentProvider.newInstance(transformerClass, item, definition, getFieldType());
238     }
239 
240     /**
241      * Exposed method used by field's factory to initialize the property {@link Converter}.<br>
242      * This allows to add additional constructor parameter if needed.<br>
243      */
244     protected Converter<?, ?> initializeConverter(Class<? extends Converter<?, ?>> converterClass) {
245         return this.componentProvider.newInstance(converterClass, item, definition, getFieldType());
246     }
247 
248 
249     /**
250      * Define the field property value type Class.<br>
251      * Return the value defined by the configuration ('type' property).<br>
252      * If this value is not defined, return the value of the overriding method {@link AbstractFieldFactory#getDefaultFieldType()}.<br>
253      * If this method is not override, return {@link UndefinedPropertyType}.<br>
254      * In this case, the {@link Transformer} will have the responsibility to define the property type.
255      */
256     protected Class<?> getFieldType() {
257         Class<?> type = getDefinitionType();
258         if (type == null) {
259             type = getDefaultFieldType();
260         }
261         return type;
262     }
263 
264     /**
265      * @return Class Type defined into the field definition or null if not defined.
266      */
267     protected Class<?> getDefinitionType() {
268         if (StringUtils.isNotBlank(definition.getType())) {
269             return DefaultPropertyUtil.getFieldTypeClass(definition.getType());
270         }
271         return null;
272     }
273 
274     /**
275      * Exposed method used by field's factory in order to define a default Field Type (decoupled from the definition).
276      */
277     protected Class<?> getDefaultFieldType() {
278         return UndefinedPropertyType.class;
279     }
280 
281     @Override
282     protected String getI18nBasename() {
283         return definition.getI18nBasename();
284     }
285 
286     /**
287      * Set all constraints linked to the field:
288      * Build Validation rules.
289      * Set Required field.
290      * Set Read Only.
291      */
292     private void setConstraints() {
293         // Set Validation
294         for (FieldValidatorDefinition validatorDefinition : definition.getValidators()) {
295             FieldValidatorFactory validatorFactory = this.fieldValidatorFactoryFactory.createFieldValidatorFactory(validatorDefinition, item);
296             if (validatorFactory != null) {
297                 field.addValidator(validatorFactory.createValidator());
298             } else {
299                 log.warn("Not able to create Validation for the following definition {}", definition.toString());
300             }
301         }
302         // Set Required
303         if (definition.isRequired()) {
304             field.setInvalidCommitted(true);
305             field.setRequired(true);
306             field.setRequiredError(definition.getRequiredErrorMessage());
307         }
308 
309         // Set ReadOnly (field property has to be updated)
310         if (field.getPropertyDataSource() != null) {
311             field.getPropertyDataSource().setReadOnly(definition.isReadOnly());
312         }
313     }
314 
315     @Override
316     public void setComponentProvider(ComponentProvider componentProvider) {
317         this.componentProvider = componentProvider;
318     }
319 
320     protected ComponentProvider getComponentProvider() {
321         return componentProvider;
322     }
323 
324 }