View Javadoc

1   /**
2    * This file Copyright (c) 2012-2013 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.jcr.wrapper;
35  
36  import info.magnolia.jcr.iterator.RangeIteratorImpl;
37  import info.magnolia.jcr.util.NodeUtil;
38  
39  import java.util.ArrayList;
40  import java.util.Collection;
41  import java.util.LinkedHashMap;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.TreeMap;
45  
46  import javax.jcr.Node;
47  import javax.jcr.NodeIterator;
48  import javax.jcr.PathNotFoundException;
49  import javax.jcr.Property;
50  import javax.jcr.PropertyIterator;
51  import javax.jcr.RepositoryException;
52  
53  import org.apache.commons.lang.StringUtils;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  /**
58   * This wrapper allows extending other nodes (mainly useful to extend configurations). A node can define
59   * a property with the name 'extends'. Its value is either an absolute or relative path. The merge is then performed as follows:
60   * <ul>
61   * <li>properties are merged and values are overwritten
62   * <li>sub nodes are merged, the original order is guaranteed, new nodes are added at the end of the list
63   * </ul>
64   * The mechanism supports multiple inheritances as such:
65   * <ul>
66   * <li>the node the current node inherits from can again extend a node
67   * <li>nodes laying deeper in the hierarchy can extend an other node
68   * </ul>
69   * 
70   * @see {@link InheritanceNodeWrapper} a class supporting content inheritance.
71   */
72  public class ExtendingNodeWrapper extends ChildWrappingNodeWrapper {
73  
74      protected static final String EXTENDING_NODE_PROPERTY = "extends";
75      protected static final String EXTENDING_NODE_PROPERTY_OVERRIDE = "override";
76  
77      private static final Logger log = LoggerFactory.getLogger(ExtendingNodeWrapper.class);
78  
79      /**
80       * Are we extending or not?
81       */
82      private boolean extending;
83  
84      /**
85       * Node that is being extended.
86       */
87      private Node extendedNode;
88  
89      /**
90       * Default constructor.
91       */
92      public ExtendingNodeWrapper(Node wrappedNode) {
93          this(wrappedNode, false);
94      }
95  
96      /**
97       * Call directly for test purposes only.
98       */
99      public ExtendingNodeWrapper(Node wrappedNode, boolean failOnError) {
100         super(wrappedNode);
101 
102         try {
103             final boolean hasExtendingProperty = getWrappedNode().hasProperty(EXTENDING_NODE_PROPERTY);
104 
105             if (hasExtendingProperty) {
106                 extending = true;
107 
108                 Property extendingNodeProperty = getWrappedNode().getProperty(EXTENDING_NODE_PROPERTY);
109 
110                 String extendingNodePath = extendingNodeProperty.getString();
111 
112                 if (StringUtils.isBlank(extendingNodePath) || extendingNodePath.equals(EXTENDING_NODE_PROPERTY_OVERRIDE)) {
113                     extending = false;
114                 }
115 
116                 if (extending) {
117                     if (isExists(extendingNodePath, getWrappedNode())) {
118                         // support multiple inheritance
119                         if (extendingNodePath.startsWith("/")) {
120                             extendedNode = getWrappedNode().getSession().getNode(extendingNodePath);
121                         } else {
122                             extendedNode = getWrappedNode().getNode(extendingNodePath);
123                         }
124                         if (!NodeUtil.isSame(getWrappedNode(), extendedNode)) {
125                             extendedNode = wrapIfNeeded(extendedNode);
126                         } else {
127                             // nodes are the same so we will not extend.
128                             extendedNode = null;
129                             extending = false;
130                             log.error("Node can't self-extend: " + getWrappedNode().getPath());
131                         }
132                     } else {
133                         String message = "Can't find referenced node for value: " + wrapped;
134                         log.error(message);
135                         extending = false;
136                         if (failOnError) {
137                             throw new RuntimeException(message);
138                         }
139                     }
140                 }
141             }
142 
143         } catch (RepositoryException e) {
144             throw new RuntimeException("Can't wrap node [" + wrapped + "]", e);
145         }
146     }
147 
148     /**
149      * Does not support the extends property but chains the two nodes directly. Each node is
150      * wrapped internally to ensure that each of them support the extends property for themselves.
151      */
152     protected ExtendingNodeWrapper(Node wrappedNode, Node extendedNode) {
153         super(wrapIfNeeded(wrappedNode));
154         extending = true;
155         try {
156             if (getWrappedNode().hasProperty(EXTENDING_NODE_PROPERTY)) {
157                 Property extendingNodeProperty = getWrappedNode().getProperty(EXTENDING_NODE_PROPERTY);
158 
159                 // check if override is not forced
160                 extending = !extendingNodeProperty.getString().equals(EXTENDING_NODE_PROPERTY_OVERRIDE);
161             }
162         } catch (RepositoryException e) {
163             throw new RuntimeException("Can't determine extends point for node [" + wrappedNode + "]", e);
164         }
165         // might extend further more
166         this.extendedNode = wrapIfNeeded(extendedNode);
167     }
168 
169     @Override
170     public boolean hasNode(String relPath) throws RepositoryException {
171         if (getWrappedNode().hasNode(relPath)) {
172             return true;
173         } else if (extending && extendedNode.hasNode(relPath)) {
174             return true;
175         } else {
176             return false;
177         }
178     }
179 
180     @Override
181     public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
182         if (getWrappedNode().hasNode(relPath)) {
183             return wrapNode(getWrappedNode().getNode(relPath));
184         } else if (extending && extendedNode.hasNode(relPath)) {
185             return extendedNode.getNode(relPath);
186         } else {
187             throw new PathNotFoundException("Node does not exists: [" + relPath + "]");
188         }
189     }
190 
191     @Override
192     public NodeIterator getNodes() throws RepositoryException {
193         return this.getNodes("*");
194     }
195 
196     @Override
197     public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
198         return this.getNodes(StringUtils.join(nameGlobs, " | "));
199     }
200 
201     @Override
202     public NodeIterator getNodes(String namePattern) throws RepositoryException {
203         Collection<Node> children = NodeUtil.getCollectionFromNodeIterator(getWrappedNode().getNodes());
204         if (extending) {
205 
206             Collection<Node> extendedNodeChildren = NodeUtil.getCollectionFromNodeIterator(extendedNode.getNodes());
207             Map<String, Node> merged = new LinkedHashMap<String, Node>();
208 
209             for (Node content : extendedNodeChildren) {
210                 merged.put(content.getName(), content);
211             }
212             for (Node content : children) {
213                 merged.put(content.getName(), content);
214             }
215             return new NodeIteratorImpl(wrapNodes(merged.values()));
216         }
217         return new NodeIteratorImpl(wrapNodes(children));
218     }
219 
220     @Override
221     public boolean hasProperty(String relPath) throws RepositoryException {
222         // extending property should be hidden
223         if (relPath.equals(EXTENDING_NODE_PROPERTY)) {
224             return false;
225         } else if (getWrappedNode().hasProperty(relPath)) {
226             return true;
227         } else if (extending && extendedNode.hasProperty(relPath)) {
228             return true;
229         } else {
230             return false;
231         }
232     }
233 
234     @Override
235     public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
236         // extending property should be hidden
237         if (relPath.equals(EXTENDING_NODE_PROPERTY)) {
238             throw new PathNotFoundException("Cannont access property [" + getWrappedNode().getPath() + "." + relPath + "]");
239         } else if (getWrappedNode().hasProperty(relPath)) {
240             return getWrappedNode().getProperty(relPath);
241         } else if (extending && extendedNode.hasProperty(relPath)) {
242             return extendedNode.getProperty(relPath);
243         } else {
244             throw new RepositoryException("Can't read property from extended node [" + extendedNode + "]");
245         }
246     }
247 
248     @Override
249     public PropertyIterator getProperties() throws RepositoryException {
250         return this.getProperties("*");
251     }
252 
253     @Override
254     public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
255         return this.getProperties(StringUtils.join(nameGlobs, " | "));
256     }
257 
258     @Override
259     public PropertyIterator getProperties(String namePattern) throws RepositoryException {
260         Collection<Property> properties = getPropertiesAsList(getWrappedNode(), namePattern);
261 
262         if (extending) {
263             Collection<Property> inheritedProperties = getPropertiesAsList(extendedNode, namePattern);
264 
265             Map<String, Property> merged = new TreeMap<String, Property>();
266 
267             for (Property prop : inheritedProperties) {
268                 merged.put(prop.getName(), prop);
269             }
270 
271             for (Property prop : properties) {
272                 merged.put(prop.getName(), prop);
273             }
274             return new PropertyIteratorImpl(merged.values());
275         }
276         return new PropertyIteratorImpl(properties);
277     }
278 
279     @Override
280     public Node wrapNode(Node node) {
281         // get the same subnode of the extended content
282         try {
283             if (extending && extendedNode.hasNode(node.getName())) {
284                 Node extendedSubNode = extendedNode.getNode(node.getName());
285                 return new ExtendingNodeWrapper(node, extendedSubNode);
286             }
287         } catch (RepositoryException e) {
288             throw new RuntimeException("Can't wrap " + node, e);
289         }
290         return wrapIfNeeded(node);
291     }
292 
293     @Override
294     public Node getWrappedNode() {
295         if (wrapped instanceof ExtendingNodeWrapper) {
296             ExtendingNodeWrapper extendingNodeWrapper = (ExtendingNodeWrapper) wrapped;
297             if (!extendingNodeWrapper.extending) {
298                 return extendingNodeWrapper.getWrappedNode();
299             }
300         }
301         return wrapped;
302     }
303 
304     @Override
305     public void setWrappedNode(Node node) {
306         // this wrapper can be used multiple times (multiple inheritance)
307         super.wrapped = node;
308     }
309 
310     /**
311      * Check if node extends another.
312      */
313     public boolean isExtending() {
314         return extending;
315     }
316 
317     /**
318      * Check if node exists.
319      */
320     private boolean isExists(String nodePath, Node parent) throws RepositoryException {
321         if (nodePath.startsWith("/")) {
322             return getWrappedNode().getSession().itemExists(nodePath);
323         }
324         return parent.hasNode(nodePath);
325     }
326 
327     /**
328      * Wraps node if needed.
329      */
330     private static Node wrapIfNeeded(Node node) {
331         if (node instanceof ExtendingNodeWrapper) {
332             return node;
333         }
334         return new ExtendingNodeWrapper(node);
335     }
336 
337     private Collection<Node> wrapNodes(Collection<Node> collection) {
338         Collection<Node> wrappedNodes = new ArrayList<Node>();
339         for (Node node : collection) {
340             wrappedNodes.add(wrapNode(node));
341         }
342         return wrappedNodes;
343     }
344 
345     /**
346      * Gets all properties from node and returns them as {@link java.util.List}.
347      * Also filters out "extends" property.
348      */
349     private static List<Property> getPropertiesAsList(Node node, String namePattern) throws RepositoryException {
350         List<Property> properties = new ArrayList<Property>();
351         PropertyIterator it = node.getProperties(namePattern);
352 
353         while (it.hasNext()) {
354             Property prop = (Property) it.next();
355             if (!prop.getName().equals(EXTENDING_NODE_PROPERTY)) {
356                 properties.add(prop);
357             }
358         }
359         return properties;
360     }
361 
362     private static class PropertyIteratorImpl extends RangeIteratorImpl<Property> implements PropertyIterator {
363 
364         public PropertyIteratorImpl(Collection<Property> properties) {
365             super(properties);
366         }
367 
368         @Override
369         public Property nextProperty() {
370             return super.next();
371         }
372     }
373 
374     private static class NodeIteratorImpl extends RangeIteratorImpl<Node> implements NodeIterator {
375 
376         public NodeIteratorImpl(Collection<Node> nodes) {
377             super(nodes);
378         }
379 
380         @Override
381         public Node nextNode() {
382             return super.next();
383         }
384     }
385 
386 }