View Javadoc
1   /**
2    * This file Copyright (c) 2013-2018 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.transformer.multi;
35  
36  import info.magnolia.jcr.iterator.FilteringPropertyIterator;
37  import info.magnolia.jcr.predicate.JCRMgnlPropertyHidingPredicate;
38  import info.magnolia.jcr.util.NodeTypes;
39  import info.magnolia.jcr.util.NodeUtil;
40  import info.magnolia.jcr.util.PropertyUtil;
41  import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper;
42  import info.magnolia.objectfactory.Components;
43  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
44  import info.magnolia.ui.form.field.definition.CompositeFieldDefinition;
45  import info.magnolia.ui.form.field.definition.ConfiguredFieldDefinition;
46  import info.magnolia.ui.form.field.definition.MultiValueFieldDefinition;
47  import info.magnolia.ui.form.field.transformer.TransformedProperty;
48  import info.magnolia.ui.form.field.transformer.Transformer;
49  import info.magnolia.ui.vaadin.integration.jcr.JcrNewNodeAdapter;
50  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
51  
52  import java.util.ArrayList;
53  import java.util.Iterator;
54  import java.util.List;
55  
56  import javax.inject.Inject;
57  import javax.jcr.Node;
58  import javax.jcr.NodeIterator;
59  import javax.jcr.Property;
60  import javax.jcr.PropertyIterator;
61  import javax.jcr.RepositoryException;
62  
63  import org.apache.commons.lang3.math.NumberUtils;
64  import org.slf4j.Logger;
65  import org.slf4j.LoggerFactory;
66  
67  import com.vaadin.v7.data.Item;
68  import com.vaadin.v7.data.util.ObjectProperty;
69  import com.vaadin.v7.data.util.PropertysetItem;
70  
71  /**
72   * Sub Nodes implementation of {@link info.magnolia.ui.form.field.transformer.Transformer} storing and retrieving properties (as {@link PropertysetItem}) displayed in MultiField.<br>
73   * <b> In opposition to {@link MultiValueChildrenNodeTransformer} this implementation handle multiple properties stored under a child node.</b> <br>
74   * These multiple properties are put/retrieve into/from a {@link PropertysetItem}.<br>
75   * Storage strategy: <br>
76   * - root node (relatedFormItem)<br>
77   * -- main child node (nodeName = field name) <br>
78   * --- child node 1 (used to store the first values set of the MultiField as a property)<br>
79   * ---- property 1 (store the first property of the first value of the MultiField)<br>
80   * ---- property 2 (store the second property of the first value of the MultiField)<br>
81   * ---- property 3 (store the third property of the first value of the MultiField)<br>
82   * ---- ...
83   * --- child node 2 (used to store the second values of the MultiField as a property)<br>
84   * ---- property 1 (store the first property of the second value of the MultiField)<br>
85   * ---- property 2 (store the second property of the second value of the MultiField)<br>
86   * ---- property 3 (store the third property of the second value of the MultiField)<br>
87   * ...<br>
88   * This implementation store/retrieve the {@link PropertysetItem} properties under the child node.<br>
89   * Used in the case of a {@link info.magnolia.ui.form.field.MultiField} contains a {@link info.magnolia.ui.form.field.CompositeField} or a {@link info.magnolia.ui.form.field.SwitchableField}.<br>
90   * In this case, {@link info.magnolia.ui.form.field.CompositeField} or {@link info.magnolia.ui.form.field.SwitchableField} will have to declare a {@link info.magnolia.ui.form.field.transformer.composite.NoOpCompositeTransformer}.
91   */
92  public class MultiValueSubChildrenNodePropertiesTransformer extends MultiValueChildrenNodeTransformer {
93  
94      private static final Logger log = LoggerFactory.getLogger(MultiValueSubChildrenNodeTransformer.class);
95  
96      private final MultiValueFieldDefinition definition;
97  
98      /**
99       * @deprecated since 5.4.2 - use {@link #MultiValueSubChildrenNodePropertiesTransformer(Item, MultiValueFieldDefinition, Class, I18NAuthoringSupport)} instead.
100      */
101     @Deprecated
102     public MultiValueSubChildrenNodePropertiesTransformer(Item relatedFormItem, MultiValueFieldDefinition definition, Class<PropertysetItem> type) {
103         this(relatedFormItem, definition, type, Components.getComponent(I18NAuthoringSupport.class));
104     }
105 
106     @Inject
107     public MultiValueSubChildrenNodePropertiesTransformer(Item relatedFormItem, MultiValueFieldDefinition definition, Class<PropertysetItem> type, I18NAuthoringSupport i18NAuthoringSupport) {
108         super(relatedFormItem, definition, type, i18NAuthoringSupport);
109         this.definition = definition;
110     }
111 
112     @Override
113     protected JcrNodeAdapter getRootItem() {
114         JcrNodeAdapter res = null;
115         try {
116             res = getOrCreateChildNode(definition.getName(), childNodeType);
117         } catch (RepositoryException re) {
118             log.warn("Not able to retrieve or create a sub node for the parent node {}", ((JcrNodeAdapter) relatedFormItem).getItemId());
119         }
120         return res;
121     }
122 
123     @Override
124     protected void handleRootitemAndParent(JcrNodeAdapter rootItem) {
125         // Attach the child item to the root item
126         if (rootItem.getChildren() != null && !rootItem.getChildren().isEmpty()) {
127             ((JcrNodeAdapter) relatedFormItem).addChild(rootItem);
128         } else {
129             ((JcrNodeAdapter) relatedFormItem).removeChild(rootItem);
130         }
131     }
132 
133     @Override
134     protected PropertysetItem getValueFromChildNode(Node child) {
135         PropertysetItem newValues = new PropertysetItem();
136         try {
137             // read values from properties
138             PropertyIterator iterator = new JCRMgnlPropertiesFilteringNodeWrapper(child).getProperties();
139             while (iterator.hasNext()) {
140                 Property jcrProperty = iterator.nextProperty();
141                 Object propertyObject = PropertyUtil.getPropertyValueObject(child, jcrProperty.getName());
142                 com.vaadin.v7.data.Property newProperty = new ObjectProperty<>(propertyObject);
143 
144                 newValues.addItemProperty(NumberUtils.isNumber(jcrProperty.getName()) ? Integer.valueOf(jcrProperty.getName()) : jcrProperty.getName(), newProperty);
145             }
146             // and read values from sub-nodes (recursively)
147             NodeIterator iter = child.getNodes();
148             while (iter.hasNext()) {
149                 Node childNode = iter.nextNode();
150                 PropertysetItem vals = getValueFromChildNode(childNode);
151                 MultiValueFieldDefinition subChildDefinition = null;
152                 for (ConfiguredFieldDefinition field : ((CompositeFieldDefinition) definition.getField()).getFields()) {
153                     if (field.getName().equals(childNode.getName())) {
154                         subChildDefinition = (MultiValueFieldDefinition) field;
155                         break;
156                     }
157                 }
158                 Transformer<PropertysetItem> subChildTransformer = (Transformer<PropertysetItem>) Components.newInstance(subChildDefinition.getTransformerClass(), new JcrNodeAdapter(childNode), subChildDefinition, PropertysetItem.class);
159                 TransformedProperty<PropertysetItem> prop = new TransformedProperty<>(subChildTransformer);
160                 prop.setValue(vals);
161                 newValues.addItemProperty(childNode.getName(), prop);
162             }
163         } catch (RepositoryException re) {
164             log.warn("Not able to read property from the following child node {}", NodeUtil.getName(child), re.getLocalizedMessage());
165         }
166         return newValues;
167     }
168 
169     @Override
170     protected void setChildItemValue(JcrNodeAdapter childItem, Object newValues) {
171         if (!(newValues instanceof PropertysetItem)) {
172             super.setChildItemValue(childItem, newValues);
173             return;
174         }
175         PropertysetItem newPropertySetValues = (PropertysetItem) newValues;
176 
177         // stored property will always be of type string since we don't store names of jcr props in any other format so we need them as strings when checking for removed ones
178         List<String> propertyNamesAsString = new ArrayList<>();
179         Iterator<?> propertyNames = newPropertySetValues.getItemPropertyIds().iterator();
180         while (propertyNames.hasNext()) {
181             // could be string, but could be also number
182             Object propertyName = propertyNames.next();
183             String propertyNameString = propertyName.toString();
184             propertyNamesAsString.add(propertyNameString);
185             com.vaadin.v7.data.Property storedProperty = childItem.getItemProperty(propertyName);
186             com.vaadin.v7.data.Property newProperty = newPropertySetValues.getItemProperty(propertyName);
187             if (newProperty != null) {
188                 if (storedProperty != null) {
189                     storedProperty.setValue(newProperty.getValue());
190                 } else {
191                     Object value = newProperty.getValue();
192                     if (value instanceof PropertysetItem) {
193                         // if this is another set, create sub-node for it and recursively call itself to set its properties (or sub-nodes)
194                         JcrNodeAdapter child;
195                         try {
196                             if (childItem.getJcrItem().hasNode(propertyNameString)) {
197                                 Node node = childItem.getJcrItem().getNode(propertyNameString);
198                                 child = new JcrNodeAdapter(node);
199                             } else {
200                                 child = new JcrNewNodeAdapter(childItem.getJcrItem(), NodeTypes.ContentNode.NAME);
201                                 child.setNodeName(propertyNameString);
202                             }
203                             childItem.addChild(child);
204                             setChildItemValue(child, value);
205                         } catch (RepositoryException e) {
206                             log.error("Failed to persist property " + propertyName + " (" + propertyName.getClass().getName() + ") with " + e.getMessage(), e);
207                             throw new RuntimeException(e);
208                         }
209                     } else {
210                         childItem.addItemProperty(propertyName, newProperty);
211                     }
212                 }
213             }
214         }
215         // mark no longer existing properties as removed to be cleaned up when saving dialog
216         try {
217             FilteringPropertyIterator iter = new FilteringPropertyIterator(childItem.getJcrItem().getProperties(), new JCRMgnlPropertyHidingPredicate());
218             while (iter.hasNext()) {
219                 Property prop = iter.nextProperty();
220                 if (!propertyNamesAsString.contains(prop.getName())) {
221                     childItem.removeItemProperty(prop.getName());
222                 }
223             }
224         } catch (RepositoryException e) {
225             log.error("Failed to remove old property from " + childItem + " with " + e.getMessage(), e);
226             throw new RuntimeException(e);
227         }
228     }
229 }