View Javadoc
1   /**
2    * This file Copyright (c) 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.composite;
35  
36  import info.magnolia.jcr.util.NodeTypes;
37  import info.magnolia.jcr.util.NodeUtil;
38  import info.magnolia.jcr.util.PropertyUtil;
39  import info.magnolia.jcr.wrapper.JCRMgnlPropertiesFilteringNodeWrapper;
40  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
41  import info.magnolia.ui.form.field.definition.CompositeFieldDefinition;
42  import info.magnolia.ui.form.field.definition.ConfiguredFieldDefinition;
43  import info.magnolia.ui.form.field.transformer.basic.BasicTransformer;
44  import info.magnolia.ui.vaadin.integration.NullItem;
45  import info.magnolia.ui.vaadin.integration.jcr.JcrNewNodeAdapter;
46  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
47  
48  import java.util.List;
49  
50  import javax.inject.Inject;
51  import javax.jcr.Node;
52  import javax.jcr.Property;
53  import javax.jcr.PropertyIterator;
54  import javax.jcr.RepositoryException;
55  
56  import org.apache.commons.lang3.StringUtils;
57  import org.apache.commons.lang3.math.NumberUtils;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  import com.vaadin.v7.data.Item;
62  import com.vaadin.v7.data.util.ObjectProperty;
63  import com.vaadin.v7.data.util.PropertysetItem;
64  
65  /**
66   * This delegating {@link info.magnolia.ui.form.field.transformer.Transformer Transformer} is dedicated to the {@link info.magnolia.ui.form.field.CompositeField CompositeField}.
67   * It considers CompositeField entry for appropriate sub-node.
68   * Delegates non-entry sub-node and property handling to their respective sub-fields.
69   * <p>
70   * The storage strategy is:
71   * <ul>
72   * <li>rootItem (relatedFormItem)
73   * <ul>
74   * <li>property 1 (delegate to respective sub-field)
75   * <li>subNode 1 (delegate to respective sub-field if it doesn't belong to a CompositeField entry)
76   * <li>compositeNode 1 (single sub-node with configured node-type for a CompositeField entry)<br>
77   * <ul>
78   * <li>...
79   * </ul>
80   * <li>...
81   * </ul>
82   * </ul>
83   */
84  public class DelegatingSubnodeCompositeTransformer extends BasicTransformer<PropertysetItem> {
85  
86      private static final Logger log = LoggerFactory.getLogger(DelegatingSubnodeCompositeTransformer.class);
87  
88      private static String DEFAULT_CHILD_NODE_TYPE = NodeTypes.Content.NAME;
89  
90      private PropertysetItem delegateAggregatorItem = new PropertysetItem();
91  
92      @Inject
93      public DelegatingSubnodeCompositeTransformer(Item relatedFormItem, ConfiguredFieldDefinition definition, Class<PropertysetItem> type, I18NAuthoringSupport i18NAuthoringSupport) {
94          super(relatedFormItem, definition, type, i18NAuthoringSupport);
95      }
96  
97      @Override
98      public PropertysetItem readFromItem() {
99          // Only read it once
100         if (!delegateAggregatorItem.getItemPropertyIds().isEmpty()) {
101             return delegateAggregatorItem;
102         }
103         JcrNodeAdapter rootItem = getRootItem();
104 
105         // The root Item was never populated, add relevant child Item based on the stored nodes.
106         if (!rootItem.hasChildItemChanges()) {
107             populateStoredItems(rootItem);
108         }
109 
110         List<ConfiguredFieldDefinition> fields = ((CompositeFieldDefinition) definition).getFields();
111         for (ConfiguredFieldDefinition fieldDefinition : fields) {
112             delegateAggregatorItem.addItemProperty(fieldDefinition.getName(), new ObjectProperty<>(rootItem));
113         }
114         return delegateAggregatorItem;
115     }
116 
117     @Override
118     public void writeToItem(PropertysetItem newValue) {
119         log.debug("CALL writeToItem");
120     }
121 
122     private String getChildNodeType(CompositeFieldDefinition definition) {
123         return StringUtils.defaultIfBlank(definition.getNodeType(), DEFAULT_CHILD_NODE_TYPE);
124     }
125 
126     protected JcrNodeAdapter getRootItem() {
127         JcrNodeAdapter res = null;
128         try {
129             res = getOrCreateChildNode(relatedFormItem, deriveLocaleAwareName(definition.getName()), getChildNodeType((CompositeFieldDefinition) definition));
130         } catch (RepositoryException re) {
131             log.warn("Not able to retrieve or create a sub node for the parent node {}", ((JcrNodeAdapter) relatedFormItem).getItemId());
132         }
133         return res;
134     }
135 
136     /**
137      * Populates the given root item with its child items and properties.
138      */
139     private void populateStoredItems(JcrNodeAdapter rootItem) {
140         if (rootItem instanceof JcrNewNodeAdapter) {
141             return;
142         }
143         try {
144             Node jcrNode = rootItem.getJcrItem();
145             if (jcrNode.hasProperties()) {
146                 PropertyIterator iterator = new JCRMgnlPropertiesFilteringNodeWrapper(jcrNode).getProperties();
147                 while (iterator.hasNext()) {
148                     Property jcrProperty = iterator.nextProperty();
149                     String propertyName = jcrProperty.getName();
150                     Object propertyValue = PropertyUtil.getPropertyValueObject(jcrNode, propertyName);
151                     com.vaadin.v7.data.Property newProperty = new ObjectProperty<>(propertyValue);
152                     rootItem.addItemProperty(NumberUtils.isNumber(propertyName) ? Integer.valueOf(propertyName) : propertyName, newProperty);
153                 }
154             }
155 
156             if (jcrNode.hasNodes()) {
157                 Iterable<Node> childNodes = NodeUtil.getNodes(jcrNode, NodeUtil.MAGNOLIA_FILTER);
158                 for (Node child : childNodes) {
159                     JcrNodeAdapter item = new JcrNodeAdapter(child);
160                     rootItem.addChild(item);
161                 }
162             }
163         } catch (RepositoryException re) {
164             log.warn("Not able to access the child nodes/properties of the following Node Identifier {}", rootItem, re);
165         }
166     }
167 
168     /**
169      * Retrieve or create a child node as {@link JcrNodeAdapter}. Method will return null for any none JcrNodeAdapter related form items.
170      */
171     private JcrNodeAdapter getOrCreateChildNode(Item parent, String childNodeName, String childNodeType) throws RepositoryException {
172         JcrNodeAdapter child;
173         if (parent instanceof NullItem) {
174             return null;
175         }
176         if (!(parent instanceof JcrNodeAdapter)) {
177             log.warn("Detected attempt to retrieve a Jcr Item from a Non Jcr Item Adapter. Will return null.");
178             return null;
179         }
180         Node node = ((JcrNodeAdapter) parent).getJcrItem();
181         if (node.hasNode(childNodeName) && !(parent instanceof JcrNewNodeAdapter)) {
182             child = new JcrNodeAdapter(node.getNode(childNodeName));
183             ((JcrNodeAdapter) parent).addChild(child);
184             return child;
185         } else if (((JcrNodeAdapter) parent).getChild(childNodeName) != null) {
186             return (JcrNodeAdapter) ((JcrNodeAdapter) parent).getChild(childNodeName);
187         }
188         child = new JcrNewNodeAdapter(node, childNodeType, childNodeName);
189         ((JcrNodeAdapter) parent).addChild(child);
190         return child;
191     }
192 }