View Javadoc

1   /**
2    * This file Copyright (c) 2011 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.inheritance;
35  
36  import info.magnolia.jcr.RuntimeRepositoryException;
37  import info.magnolia.jcr.decoration.AbstractContentDecorator;
38  import info.magnolia.jcr.decoration.ContentDecoratorNodeWrapper;
39  import info.magnolia.jcr.iterator.ChainedNodeIterator;
40  import info.magnolia.jcr.iterator.FilteringNodeIterator;
41  import info.magnolia.jcr.iterator.RangeIteratorImpl;
42  import info.magnolia.jcr.predicate.AbstractPredicate;
43  import info.magnolia.jcr.util.NodeUtil;
44  
45  import javax.jcr.Node;
46  import javax.jcr.NodeIterator;
47  import javax.jcr.PathNotFoundException;
48  import javax.jcr.Property;
49  import javax.jcr.PropertyIterator;
50  import javax.jcr.RepositoryException;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.Collections;
54  import java.util.HashSet;
55  import java.util.Iterator;
56  import java.util.List;
57  
58  /**
59   * Provides inheritance on JCR level by applying wrapper objects. The inheritance destination node gets nodes and
60   * properties from a list of source nodes. Subclasses can customize which nodes are inherited and how the inherited
61   * nodes are ordered.
62   *
63   * <h3>Limitations:</h3>
64   * <ul><li>The inherited nodes are returned while querying for them on the destination node. The will not be visible when
65   * queried for on other nodes or the session.</li>
66   * <li>The inherited nodes will *not* have their path or depth adjusted to match the destination node</li>
67   * <li>When inheritance results in multiple nodes that have the same name the index on nodes are not adjusted</li>
68   * </ul>
69   *
70   * @version $Id$
71   */
72  public class InheritanceContentDecorator extends AbstractContentDecorator {
73  
74      // The node to which we inherit
75      private final Node destination;
76  
77      // The nodes we inherit from in bottom-up order
78      private List<Node> sources = new ArrayList<Node>();
79  
80      private AbstractPredicate<Node> childInheritancePredicate = new AbstractPredicate<Node>() {
81          @Override
82          public boolean evaluateTyped(Node node) {
83              try {
84                  return isSourceChildInherited(node);
85              } catch (RepositoryException e) {
86                  throw new RuntimeRepositoryException(e);
87              }
88          }
89      };
90  
91      public InheritanceContentDecorator(Node destination) throws RepositoryException {
92          this.destination = destination;
93      }
94  
95      @Override
96      public Node wrapNode(Node node) {
97          try {
98              if (NodeUtil.isSame(destination, node)) {
99                  return new DestinationNodeInheritanceNodeWrapper(node);
100             } else {
101                 return new OtherNodeInheritanceNodeWrapper(node);
102             }
103         } catch (RepositoryException e) {
104             throw new RuntimeRepositoryException(e);
105         }
106     }
107 
108     public Node getDestination() {
109         return destination;
110     }
111 
112     /**
113      * Returns a predicate that delegates to {@link #isSourceChildInherited(Node)}.
114      */
115     private AbstractPredicate<Node> getChildInheritancePredicate() {
116 
117         if (childInheritancePredicate != null) {
118             return childInheritancePredicate;
119         }
120 
121         childInheritancePredicate = new AbstractPredicate<Node>() {
122             @Override
123             public boolean evaluateTyped(Node node) {
124                 try {
125                     return isSourceChildInherited(node);
126                 } catch (RepositoryException e) {
127                     throw new RuntimeRepositoryException(e);
128                 }
129             }
130         };
131 
132         return childInheritancePredicate;
133     }
134 
135     public void addSource(Node source) {
136         this.sources.add(source);
137     }
138 
139     /**
140      * Decides if a node inherits child nodes. By default always returns true.
141      *
142      * @param node the destination node or a source node
143      * @return true if the node inherits nodes
144      * @throws RepositoryException
145      */
146     protected boolean inheritsNodes(Node node) throws RepositoryException {
147         return true;
148     }
149 
150     /**
151      * Decides if a node inherits properties. By default always returns true.
152      *
153      * @param node the destination node or a source node
154      * @return true if the node inherits properties
155      * @throws RepositoryException
156      */
157     protected boolean inheritsProperties(Node node) throws RepositoryException {
158         return true;
159     }
160 
161     /**
162      * Decides if a specific child node of one of the source should be inherited. By default always returns true.
163      *
164      * @param node a child of one of the source nodes
165      * @return true if the node is inherited
166      * @throws RepositoryException
167      */
168     protected boolean isSourceChildInherited(Node node) throws RepositoryException {
169         return true;
170     }
171 
172     /**
173      * Sorts the inherited nodes and provides a {@link NodeIterator} representing that order. By default orders nodes
174      * from the top-most source first and nodes from the destination last.
175      *
176      * @param destinationChildren children of the destination node
177      * @param sourceChildren children of each of the source nodes in bottom-up order
178      * @return
179      * @throws javax.jcr.RepositoryException
180      */
181     protected NodeIterator sortInheritedNodes(NodeIterator destinationChildren, List<NodeIterator> sourceChildren) throws RepositoryException {
182         Collections.reverse(sourceChildren);
183         sourceChildren.add(destinationChildren);
184         return new FilteringNodeIterator(new ChainedNodeIterator(sourceChildren), getChildInheritancePredicate());
185     }
186 
187     /**
188      * Combines the inherited properties and provides them as a {@link PropertyIterator}. By default properties in the
189      * destination node overrides properties from source nodes. Properties on source node have preference in a bottom to
190      * top order.
191      *
192      * @param destinationProperties properties of the destination node
193      * @param sourceProperties properties of the source nodes in bottom-up order
194      * @return
195      * @throws RepositoryException
196      */
197     protected PropertyIterator combinePropertyIterators(PropertyIterator destinationProperties, List<PropertyIterator> sourceProperties) throws RepositoryException {
198         HashSet<String> names = new HashSet<String>();
199         ArrayList<Property> properties = new ArrayList<Property>();
200         while (destinationProperties.hasNext()) {
201             Property property = destinationProperties.nextProperty();
202             names.add(property.getName());
203             properties.add(property);
204         }
205         for (PropertyIterator propertyIterator : sourceProperties) {
206             while (propertyIterator.hasNext()) {
207                 Property property = (Property) propertyIterator.next();
208                 if (!names.contains(property.getName())) {
209                     names.add(property.getName());
210                     properties.add(property);
211                 }
212             }
213         }
214         return new PropertyIteratorImpl(properties);
215     }
216 
217     /**
218      * Wrapper applied to nodes other than the destination node.
219      * 
220      * @version $Id$
221      */
222     private class OtherNodeInheritanceNodeWrapper extends ContentDecoratorNodeWrapper implements InheritanceNodeWrapper {
223 
224         public OtherNodeInheritanceNodeWrapper(Node node) {
225             super(node, InheritanceContentDecorator.this);
226         }
227 
228         @Override
229         public boolean isInherited() {
230             try {
231 
232                 Node wrappedNode = getWrappedNode();
233 
234                 if (isChildOf(wrappedNode, destination)) {
235                     return false;
236                 }
237 
238                 Node n = destination;
239                 Iterator<Node> iterator = sources.iterator();
240                 while (iterator.hasNext() && inheritsNodes(n)) {
241                     n = iterator.next();
242                     if (isChildOf(wrappedNode, n) && isSourceChildInherited(wrappedNode)) {
243                         return true;
244                     }
245                 }
246 
247                 return false;
248             } catch (RepositoryException e) {
249                 throw new RuntimeRepositoryException(e);
250             }
251         }
252 
253         private boolean isChildOf(Node child, Node parent) {
254             try {
255                 return parent.getDepth() == 0 || child.getPath().startsWith(parent.getPath() + "/");
256             } catch (RepositoryException e) {
257                 throw new RuntimeRepositoryException(e);
258             }
259         }
260     }
261 
262     /**
263      * Wrapper applied to the destination node. Uses
264      *
265      * @version $Id$
266      */
267     public class DestinationNodeInheritanceNodeWrapper extends ContentDecoratorNodeWrapper implements InheritanceNodeWrapper {
268 
269         public DestinationNodeInheritanceNodeWrapper(Node node) {
270             super(node, InheritanceContentDecorator.this);
271         }
272 
273         @Override
274         public boolean hasNode(String relPath) throws RepositoryException {
275             if (super.hasNode(relPath)) {
276                 return true;
277             }
278             Node current = getWrappedNode();
279             Iterator<Node> iterator = sources.iterator();
280             while (iterator.hasNext() && inheritsNodes(current)) {
281                 current = iterator.next();
282                 if (current.hasNode(relPath) && isSourceChildInherited(current.getNode(relPath))) {
283                     return true;
284                 }
285             }
286             return false;
287         }
288 
289         @Override
290         public Node getNode(String relPath) throws PathNotFoundException, RepositoryException {
291             if (super.hasNode(relPath)) {
292                 return super.getNode(relPath);
293             }
294             Node current = getWrappedNode();
295             Iterator<Node> iterator = sources.iterator();
296             while (iterator.hasNext() && inheritsNodes(current)) {
297                 current = iterator.next();
298                 if (current.hasNode(relPath)) {
299                     Node node = current.getNode(relPath);
300                     if (isSourceChildInherited(node)) {
301                         return wrapNode(node);
302                     }
303                 }
304             }
305             throw new PathNotFoundException(relPath);
306         }
307 
308         @Override
309         public NodeIterator getNodes() throws RepositoryException {
310             List<NodeIterator> nodes = new ArrayList<NodeIterator>();
311             Node current = getWrappedNode();
312             Iterator<Node> iterator = sources.iterator();
313             while (iterator.hasNext() && inheritsNodes(current)) {
314                 current = iterator.next();
315                 nodes.add(current.getNodes());
316             }
317             return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(), nodes));
318         }
319 
320         @Override
321         public NodeIterator getNodes(String namePattern) throws RepositoryException {
322             List<NodeIterator> nodes = new ArrayList<NodeIterator>();
323             Node current = getWrappedNode();
324             Iterator<Node> iterator = sources.iterator();
325             while (iterator.hasNext() && inheritsNodes(current)) {
326                 current = iterator.next();
327                 nodes.add(current.getNodes(namePattern));
328             }
329             return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(namePattern), nodes));
330         }
331 
332         @Override
333         public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
334             List<NodeIterator> nodes = new ArrayList<NodeIterator>();
335             Node current = getWrappedNode();
336             Iterator<Node> iterator = sources.iterator();
337             while (iterator.hasNext() && inheritsNodes(current)) {
338                 current = iterator.next();
339                 nodes.add(current.getNodes(nameGlobs));
340             }
341             return super.wrapNodeIterator(sortInheritedNodes(getWrappedNode().getNodes(nameGlobs), nodes));
342         }
343 
344         @Override
345         public boolean hasProperty(String relPath) throws RepositoryException {
346             if (super.hasProperty(relPath)) {
347                 return true;
348             }
349             Node current = getWrappedNode();
350             Iterator<Node> iterator = sources.iterator();
351             while (iterator.hasNext() && inheritsProperties(current)) {
352                 current = iterator.next();
353                 if (current.hasProperty(relPath)) {
354                     return true;
355                 }
356             }
357             return false;
358         }
359 
360         @Override
361         public Property getProperty(String relPath) throws PathNotFoundException, RepositoryException {
362             if (super.hasProperty(relPath)) {
363                 return super.getProperty(relPath);
364             }
365             Node current = getWrappedNode();
366             Iterator<Node> iterator = sources.iterator();
367             while (iterator.hasNext() && inheritsProperties(current)) {
368                 current = iterator.next();
369                 if (current.hasProperty(relPath)) {
370                     return wrapProperty(current.getProperty(relPath));
371                 }
372             }
373             throw new PathNotFoundException(relPath);
374         }
375 
376         @Override
377         public PropertyIterator getProperties() throws RepositoryException {
378             ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
379             Node current = getWrappedNode();
380             Iterator<Node> iterator = sources.iterator();
381             while (iterator.hasNext() && inheritsProperties(current)) {
382                 current = iterator.next();
383                 properties.add(current.getProperties());
384             }
385             return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(), properties));
386         }
387 
388         @Override
389         public PropertyIterator getProperties(String namePattern) throws RepositoryException {
390             ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
391             Node current = getWrappedNode();
392             Iterator<Node> iterator = sources.iterator();
393             while (iterator.hasNext() && inheritsProperties(current)) {
394                 current = iterator.next();
395                 properties.add(current.getProperties(namePattern));
396             }
397             return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(namePattern), properties));
398         }
399 
400         @Override
401         public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
402             ArrayList<PropertyIterator> properties = new ArrayList<PropertyIterator>();
403             Node current = getWrappedNode();
404             Iterator<Node> iterator = sources.iterator();
405             while (iterator.hasNext() && inheritsProperties(current)) {
406                 current = iterator.next();
407                 properties.add(current.getProperties(nameGlobs));
408             }
409             return super.wrapPropertyIterator(combinePropertyIterators(super.getProperties(nameGlobs), properties));
410         }
411 
412         @Override
413         public boolean isInherited() {
414             return false;
415         }
416     }
417 
418     private static class PropertyIteratorImpl extends RangeIteratorImpl implements PropertyIterator {
419 
420         public PropertyIteratorImpl(Collection<Property> collection) {
421             super(collection);
422         }
423 
424         @Override
425         public Property nextProperty() {
426             return (Property) super.next();
427         }
428     }
429 }