View Javadoc

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