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.util;
35  
36  import info.magnolia.cms.core.MgnlNodeType;
37  import info.magnolia.cms.security.AccessDeniedException;
38  import info.magnolia.cms.security.PermissionUtil;
39  import info.magnolia.context.MgnlContext;
40  import info.magnolia.jcr.RuntimeRepositoryException;
41  import info.magnolia.jcr.iterator.NodeIterableAdapter;
42  import info.magnolia.jcr.predicate.AbstractPredicate;
43  import info.magnolia.jcr.wrapper.DelegateNodeWrapper;
44  import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper;
45  
46  import java.util.ArrayList;
47  import java.util.Collection;
48  import java.util.HashSet;
49  import java.util.Iterator;
50  import java.util.LinkedHashSet;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.NoSuchElementException;
54  import java.util.Queue;
55  import java.util.Set;
56  
57  import javax.jcr.Node;
58  import javax.jcr.NodeIterator;
59  import javax.jcr.PathNotFoundException;
60  import javax.jcr.Property;
61  import javax.jcr.RepositoryException;
62  import javax.jcr.Session;
63  import javax.jcr.nodetype.NodeType;
64  import javax.jcr.nodetype.NodeTypeManager;
65  import javax.jcr.query.Row;
66  import javax.jcr.query.RowIterator;
67  
68  import org.apache.commons.lang.StringUtils;
69  import org.apache.jackrabbit.JcrConstants;
70  import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
71  import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
72  import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
73  import org.apache.jackrabbit.commons.predicate.Predicate;
74  import org.slf4j.Logger;
75  import org.slf4j.LoggerFactory;
76  
77  /**
78   * Various utility methods to collect data from JCR repository.
79   *
80   * @version $Id$
81   */
82  public class NodeUtil {
83  
84      private static final Logger log = LoggerFactory.getLogger(NodeUtil.class);
85  
86      /**
87       * Node filter accepting everything except nodes with namespace jcr (version and system store).
88       */
89      public static Predicate ALL_NODES_EXCEPT_JCR_FILTER = new AbstractPredicate<Node>() {
90          @Override
91          public boolean evaluateTyped(Node node) {
92              try {
93                  return !node.getName().startsWith(MgnlNodeType.JCR_PREFIX);
94              } catch (RepositoryException e) {
95                  return false;
96              }
97          }
98      };
99  
100     /**
101      * Node filter accepting everything except meta data and jcr types.
102      */
103     public static AbstractPredicate<Node> EXCLUDE_META_DATA_FILTER = new AbstractPredicate<Node>() {
104 
105         @Override
106         public boolean evaluateTyped(Node node) {
107             try {
108                 return !node.getName().startsWith(MgnlNodeType.JCR_PREFIX)
109                 && !NodeUtil.isNodeType(node, MgnlNodeType.NT_METADATA);
110             } catch (RepositoryException e) {
111                 return false;
112             }
113         }
114     };
115 
116     /**
117      * Node filter accepting all nodes of a type with namespace mgnl.
118      */
119     public static AbstractPredicate<Node> MAGNOLIA_FILTER = new AbstractPredicate<Node>() {
120 
121         @Override
122         public boolean evaluateTyped(Node node) {
123 
124             try {
125                 String nodeTypeName = node.getPrimaryNodeType().getName();
126                 // accept only "magnolia" nodes
127                 return nodeTypeName.startsWith(MgnlNodeType.MGNL_PREFIX);
128             } catch (RepositoryException e) {
129                 // TODO should we really mask this error? shouldn't it be thrown instead?
130                 log.error("Unable to read nodetype for node {}", getNodePathIfPossible(node));
131             }
132             return false;
133         }
134     };
135 
136     /**
137      * Get a Node by identifier.
138      */
139     public static Node getNodeByIdentifier(String workspace, String identifier) throws RepositoryException {
140         Node target = null;
141         Session jcrSession;
142         if (workspace == null || identifier == null) {
143             return target;
144         }
145 
146         jcrSession = MgnlContext.getJCRSession(workspace);
147         if (jcrSession != null) {
148             target = jcrSession.getNodeByIdentifier(identifier);
149         }
150         return target;
151     }
152 
153     /**
154      * from default content.
155      */
156     public static boolean hasMixin(Node node, String mixinName) throws RepositoryException {
157         if (StringUtils.isBlank(mixinName)) {
158             throw new IllegalArgumentException("Mixin name can't be empty.");
159         }
160         for (NodeType type : node.getMixinNodeTypes()) {
161             if (mixinName.equals(type.getName())) {
162                 return true;
163             }
164         }
165         return false;
166     }
167 
168     /**
169      * TODO dlipp: better name? Clear javadoc! Move to MetaDataUtil, do not assign method-param! TODO cringele :
170      * shouldn't @param nodeType be aligned to JCR API? There it is nodeTypeName, nodeType is used for NodeType object
171      */
172     public static boolean isNodeType(Node node, String type) throws RepositoryException {
173         node = NodeUtil.deepUnwrap(node, JCRPropertiesFilteringNodeWrapper.class);
174         final String actualType = node.getProperty(MgnlNodeType.JCR_PRIMARY_TYPE).getString();
175         // if the node is frozen, and we're not looking specifically for frozen nodes, then we compare with the original
176         // node type
177         if (MgnlNodeType.NT_FROZENNODE.equals(actualType) && !(MgnlNodeType.NT_FROZENNODE.equals(type))) {
178             final Property p = node.getProperty(MgnlNodeType.JCR_FROZEN_PRIMARY_TYPE);
179             final String s = p.getString();
180             NodeTypeManager ntManager = node.getSession().getWorkspace().getNodeTypeManager();
181             NodeType primaryNodeType = ntManager.getNodeType(s);
182             return primaryNodeType.isNodeType(type);
183         }
184         return node.isNodeType(type);
185     }
186 
187     public static Node unwrap(Node node) throws RepositoryException {
188         Node unwrappedNode = node;
189         while (unwrappedNode instanceof DelegateNodeWrapper) {
190             unwrappedNode = ((DelegateNodeWrapper) unwrappedNode).getWrappedNode();
191         }
192         return unwrappedNode;
193     }
194 
195     /**
196      * Removes a wrapper by type. The wrapper can be deep in a chain of wrappers in which case wrappers before it will
197      * be cloned creating a new chain that leads to the same real node.
198      */
199     public static Node deepUnwrap(Node node, Class<? extends DelegateNodeWrapper> wrapper) {
200         if (node instanceof DelegateNodeWrapper) {
201             return ((DelegateNodeWrapper) node).deepUnwrap(wrapper);
202         }
203         return node;
204     }
205 
206     /**
207      * Removes all wrappers of a given type. Other wrappers are cloned creating a new chain that leads to the same real
208      * node.
209      */
210     public static Node deepUnwrapAll(Node node, Class<? extends DelegateNodeWrapper> wrapperClass) {
211         while (node instanceof DelegateNodeWrapper) {
212             Node unwrapped = ((DelegateNodeWrapper) node).deepUnwrap(wrapperClass);
213             // If the unwrapping had no effect we're done
214             if (unwrapped == node) {
215                 break;
216             }
217             node = unwrapped;
218         }
219         return node;
220     }
221 
222     public static boolean isWrappedWith(Node node, Class<? extends DelegateNodeWrapper> wrapper) {
223         if (wrapper.isInstance(node)){
224             return true;
225         }
226 
227         if (node instanceof DelegateNodeWrapper) {
228             return isWrappedWith(((DelegateNodeWrapper)node).getWrappedNode(), wrapper);
229         }
230         return false;
231     }
232 
233 
234     /**
235      * Convenience - delegate to {@link Node#orderBefore(String, String)}.
236      */
237     public static void orderBefore(Node node, String siblingName) throws RepositoryException {
238         node.getParent().orderBefore(node.getName(), siblingName);
239     }
240 
241     /**
242      * Orders the node directly after a given sibling. If no sibling is specified the node is placed first.
243      */
244     public static void orderAfter(Node node, String siblingName) throws RepositoryException {
245 
246         if (siblingName == null) {
247             orderFirst(node);
248             return;
249         }
250 
251         Node parent = node.getParent();
252         Node sibling = parent.getNode(siblingName);
253         Node siblingAfter = getSiblingAfter(sibling);
254 
255         if (siblingAfter == null) {
256             orderLast(node);
257             return;
258         }
259 
260         // Move the node before the sibling directly after the target sibling
261         parent.orderBefore(node.getName(), siblingAfter.getName());
262     }
263 
264     /**
265      * Orders the node first among its siblings.
266      */
267     public static void orderFirst(Node node) throws RepositoryException {
268         Node parent = node.getParent();
269         NodeIterator siblings = parent.getNodes();
270         Node firstSibling = siblings.nextNode();
271         if (!firstSibling.isSame(node)) {
272             parent.orderBefore(node.getName(), firstSibling.getName());
273         }
274     }
275 
276     /**
277      * Orders the node last among its siblings.
278      */
279     public static void orderLast(Node node) throws RepositoryException {
280         node.getParent().orderBefore(node.getName(), null);
281     }
282 
283     /**
284      * Orders the node up one step among its siblings. If the node is the only sibling or the first sibling this method
285      * has no effect.
286      */
287     public static void orderNodeUp(Node node) throws RepositoryException {
288         Node siblingBefore = getSiblingBefore(node);
289         if (siblingBefore != null) {
290             node.getParent().orderBefore(node.getName(), siblingBefore.getName());
291         }
292     }
293 
294     /**
295      * Orders the node down one step among its siblings. If the node is the only sibling or the last sibling this method
296      * has no effect.
297      */
298     public static void orderNodeDown(Node node) throws RepositoryException {
299         Node siblingAfter = getSiblingAfter(node);
300         if (siblingAfter != null) {
301             node.getParent().orderBefore(siblingAfter.getName(), node.getName());
302         }
303     }
304 
305     public static Node getSiblingBefore(Node node) throws RepositoryException {
306         Node parent = node.getParent();
307         NodeIterator siblings = parent.getNodes();
308         Node previousSibling = null;
309         while (siblings.hasNext()) {
310             Node sibling = siblings.nextNode();
311             if (isSame(node, sibling)) {
312                 return previousSibling;
313             }
314             previousSibling = sibling;
315         }
316         return null;
317     }
318 
319     public static Node getSiblingAfter(Node node) throws RepositoryException {
320         Node parent = node.getParent();
321         NodeIterator siblings = parent.getNodes();
322         while (siblings.hasNext()) {
323             Node sibling = siblings.nextNode();
324             if (isSame(node, sibling)) {
325                 break;
326             }
327         }
328         return siblings.hasNext() ? siblings.nextNode() : null;
329     }
330 
331     /**
332      * Gets the siblings of this node.
333      * @param node node from which will be siblings retrieved
334      * @param nodeTypeName requested type of siblings nodes
335      * @return list of siblings of the given Node (only the given node is excluded)
336      */
337     public static Iterable<Node> getSiblings(Node node) throws RepositoryException {
338         Node parent = node.getParent();
339         Iterable<Node> allSiblings = NodeUtil.getNodes(parent);
340         List<Node> siblings = new ArrayList<Node>();
341 
342         for(Node sibling: allSiblings) {
343             if (!NodeUtil.isSame(node, sibling)) {
344                 siblings.add(sibling);
345             }
346         }
347         return siblings;
348     }
349 
350     /**
351      * Gets the siblings of this node with certain type.
352      * @param node node from which will be siblings retrieved
353      * @param nodeTypeName requested type of siblings nodes
354      * @return list of siblings of the given Node (the given node is excluded)
355      */
356     public static Iterable<Node> getSiblings(Node node, String nodeTypeName) throws RepositoryException {
357         Node parent = node.getParent();
358         Iterable<Node> allSiblings = NodeUtil.getNodes(parent, nodeTypeName);
359         List<Node> sameTypeSiblings = new ArrayList<Node>();
360 
361         for(Node sibling: allSiblings) {
362             if (!NodeUtil.isSame(node, sibling)) {
363                 sameTypeSiblings.add(sibling);
364             }
365         }
366         return sameTypeSiblings;
367     }
368 
369     /**
370      * Gets the siblings of this node according to predicate.
371      * @param node node from which will be siblings retrieved
372      * @param predicate predicate
373      * @return list of siblings of the given Node (the given node is excluded)
374      */
375     public static Iterable<Node> getSiblings(Node node, Predicate predicate) throws RepositoryException {
376         Node parent = node.getParent();
377         Iterable<Node> allSiblings = NodeUtil.getNodes(parent, predicate);
378         List<Node> sameTypeSiblings = new ArrayList<Node>();
379 
380         for(Node sibling: allSiblings) {
381             if (!NodeUtil.isSame(node, sibling)) {
382                 sameTypeSiblings.add(sibling);
383             }
384         }
385         return sameTypeSiblings;
386     }
387 
388     /**
389      * Gets the siblings before this node.
390      * @param node node from which will be siblings retrieved
391      * @return list of siblings before the given Node (the given node is excluded)
392      */
393     public static Iterable<Node> getSiblingsBefore(Node node) throws RepositoryException {
394         int toIndex = 0;
395         Node parent = node.getParent();
396         List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent));
397 
398         for(Node sibling: allSiblings) {
399             if (NodeUtil.isSame(node, sibling)) {
400                 break;
401             }
402             toIndex++;
403         }
404         return allSiblings.subList(0, toIndex);
405     }
406 
407     /**
408      * Gets the siblings after this node.
409      * @param node node from which will be siblings retrieved
410      * @return list of siblings after the given Node (the given node is excluded)
411      */
412     public static Iterable<Node> getSiblingsAfter(Node node) throws RepositoryException {
413         int fromIndex = 0;
414         Node parent = node.getParent();
415         List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent));
416 
417         for(Node sibling: allSiblings) {
418             if (NodeUtil.isSame(node, sibling)) {
419                 fromIndex++;
420                 break;
421             }
422             fromIndex++;
423         }
424         return allSiblings.subList(fromIndex, allSiblings.size());
425     }
426 
427     /**
428      * Gets the siblings before this node with certain type.
429      * @param node node from which will be siblings retrieved
430      * @param nodeTypeName requested type of siblings nodes
431      * @return list of siblings before the given Node (the given node is excluded)
432      */
433     public static Iterable<Node> getSiblingsBefore(Node node, String nodeTypeName) throws RepositoryException {
434         Node parent = node.getParent();
435         Iterable<Node> allSiblings = NodeUtil.getNodes(parent);
436         List<Node> sameTypeSiblings = new ArrayList<Node>();
437 
438         for(Node sibling: allSiblings) {
439             if (NodeUtil.isSame(node, sibling)) {
440                 break;
441             }
442             if (isNodeType(sibling, nodeTypeName)) {
443                 sameTypeSiblings.add(sibling);
444             }
445         }
446         return sameTypeSiblings;
447     }
448 
449     /**
450      * Gets the siblings after this node with certain type.
451      * @param node node from which will be siblings retrieved
452      * @param nodeTypeName requested type of siblings nodes
453      * @return list of siblings after the given Node (the given node is excluded)
454      */
455     public static Iterable<Node> getSiblingsAfter(Node node, String nodeTypeName) throws RepositoryException {
456         Node parent = node.getParent();
457         List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent));
458         int fromIndex = 0;
459 
460         for(Node sibling: allSiblings) {
461             fromIndex++;
462             if (NodeUtil.isSame(node, sibling)) {
463                 break;
464             }
465         }
466 
467         List<Node> sameTypeSiblings = new ArrayList<Node>();
468         for(Node sibling: allSiblings.subList(fromIndex, allSiblings.size())) {
469             if (isNodeType(sibling, nodeTypeName)) {
470                 sameTypeSiblings.add(sibling);
471             }
472         }
473         return sameTypeSiblings;
474     }
475 
476     public static void moveNode(Node nodeToMove, Node newParent) throws RepositoryException {
477         if (!isSame(newParent, nodeToMove.getParent())) {
478             String newPath = combinePathAndName(newParent.getPath(), nodeToMove.getName());
479             nodeToMove.getSession().move(nodeToMove.getPath(), newPath);
480         }
481     }
482 
483     public static void moveNodeBefore(Node nodeToMove, Node target) throws RepositoryException {
484         Node targetParent = target.getParent();
485         moveNode(nodeToMove, targetParent);
486         targetParent.orderBefore(nodeToMove.getName(), target.getName());
487     }
488 
489     public static void moveNodeAfter(Node nodeToMove, Node target) throws RepositoryException {
490         Node targetParent = target.getParent();
491         moveNode(nodeToMove, targetParent);
492         orderAfter(nodeToMove, target.getName());
493     }
494 
495     public static boolean isFirstSibling(Node node) throws RepositoryException {
496         Node parent = node.getParent();
497         NodeIterator nodes = parent.getNodes();
498         return isSame(nodes.nextNode(), node);
499     }
500 
501     /**
502      * Check if node1 and node2 are siblings.
503      */
504     public static boolean isSameNameSiblings(Node node1, Node node2) throws RepositoryException {
505         Node parent1 = node1.getParent();
506         Node parent2 = node2.getParent();
507         return isSame(parent1, parent2) && node1.getName().equals(node2.getName());
508     }
509 
510     public static boolean isLastSibling(Node node) throws RepositoryException {
511         Node parent = node.getParent();
512         NodeIterator nodes = parent.getNodes();
513         Node last = null;
514         while (nodes.hasNext()) {
515             last = nodes.nextNode();
516         }
517         return isSame(last, node);
518     }
519 
520     public static void renameNode(Node node, String newName) throws RepositoryException {
521         if (node.getName().equals(newName)) {
522             return;
523         }
524         final Node parent = node.getParent();
525         final String newPath = combinePathAndName(parent.getPath(), newName);
526         final Node siblingAfter = NodeUtil.getSiblingAfter(node);
527 
528         node.getSession().move(node.getPath(), newPath);
529 
530         if (siblingAfter != null) {
531             parent.orderBefore(newName, siblingAfter.getName());
532         }
533     }
534 
535 
536     /**
537      * @return Whether the provided node as the provided permission or not.
538      * @throws RuntimeException
539      *             in case of RepositoryException.
540      */
541     public static boolean isGranted(Node node, long permissions) {
542         try {
543             return PermissionUtil.isGranted(node, permissions);
544         } catch (RepositoryException e) {
545             // TODO dlipp - apply consistent ExceptionHandling
546             throw new RuntimeException(e);
547         }
548     }
549 
550     /**
551      * Returns true if both arguments represents the same node. In case the nodes are wrapped the comparison is done one
552      * the actual nodes behind the wrappers.
553      */
554     public static boolean isSame(Node lhs, Node rhs) throws RepositoryException {
555         return unwrap(lhs).isSame(unwrap(rhs));
556     }
557 
558     public static String combinePathAndName(String path, String name) {
559         if ("/".equals(path)) {
560             return "/" + name;
561         }
562         return path + "/" + name;
563     }
564 
565     /**
566      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
567      * method will simply return it.
568      */
569     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName) throws RepositoryException, PathNotFoundException, AccessDeniedException {
570         return createPath(parent, relPath, primaryNodeTypeName, false);
571     }
572 
573     /**
574      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
575      * method will simply return it.
576      */
577     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName, boolean save) throws RepositoryException, PathNotFoundException, AccessDeniedException {
578         // remove leading /
579         String currentPath = StringUtils.removeStart(relPath, "/");
580 
581         if (StringUtils.isEmpty(currentPath)) {
582             return parent;
583         }
584 
585         Node root = parent;
586         String[] names = currentPath.split("/");
587 
588         for (int i = 0; i < names.length; i++) {
589             String name = names[i];
590             if (root.hasNode(name)) {
591                 root = root.getNode(name);
592             } else {
593                 final Node newNode = root.addNode(name, primaryNodeTypeName);
594                 if (newNode.canAddMixin(JcrConstants.MIX_LOCKABLE)) {
595                     newNode.addMixin(JcrConstants.MIX_LOCKABLE);
596                 }
597                 if (save) {
598                     root.getSession().save();
599                 }
600                 root = newNode;
601             }
602         }
603         return root;
604     }
605 
606     /**
607      * Visits the given node and then all of nodes beneath it except for metadata nodes and nodes of jcr type.
608      */
609     public static void visit(Node node, NodeVisitor visitor) throws RepositoryException {
610         visit(node, visitor, EXCLUDE_META_DATA_FILTER);
611     }
612 
613     public static void visit(Node node, NodeVisitor visitor, Predicate predicate) throws RepositoryException {
614         // TODO should it really visit the start node even if it doesn't match the filter?
615         visitor.visit(node);
616         for (Node child : getNodes(node, predicate)) {
617             visit(child, visitor, predicate);
618         }
619         if (visitor instanceof PostNodeVisitor) {
620             ((PostNodeVisitor) visitor).postVisit(node);
621         }
622     }
623 
624     public static Iterable<Node> getNodes(Node parent, Predicate predicate) throws RepositoryException {
625         return asIterable(new FilteringNodeIterator(parent.getNodes(), predicate));
626     }
627 
628     public static Iterable<Node> getNodes(Node parent) throws RepositoryException {
629         return getNodes(parent, EXCLUDE_META_DATA_FILTER);
630     }
631 
632     public static Iterable<Node> getNodes(Node parent, String nodeTypeName) throws RepositoryException {
633         return getNodes(parent, new NodeTypePredicate(nodeTypeName, false));
634     }
635 
636     public static Iterable<Node> asIterable(NodeIterator iterator) {
637         return new NodeIterableAdapter(iterator);
638     }
639 
640     public static List<Node> asList(Iterable<Node> nodes) {
641         List<Node> nodesList = new ArrayList<Node>();
642         for (Node node : nodes) {
643             nodesList.add(node);
644         }
645         return nodesList;
646     }
647 
648     /**
649      * This method return the node's name on success, otherwise it handles the {@link RepositoryException} by throwing a
650      * {@link RuntimeRepositoryException}.
651      */
652     public static String getName(Node content) {
653         try {
654             return content.getName();
655         } catch (RepositoryException e) {
656             throw new RuntimeRepositoryException(e);
657         }
658     }
659 
660     /**
661      * Get all children (by recursion) using MAGNOLIA_FILTER (filter accepting all nodes of a type with namespace mgnl).
662      */
663     public static Iterable<Node> collectAllChildren(Node node) throws RepositoryException {
664         List<Node> nodes = new ArrayList<Node>();
665         return collectAllChildren(nodes, node, MAGNOLIA_FILTER);
666     }
667 
668     /**
669      * Get all children (by recursion) using a Predicate.
670      */
671     public static Iterable<Node> collectAllChildren(Node node, Predicate predicate) throws RepositoryException {
672         List<Node> nodes = new ArrayList<Node>();
673         return collectAllChildren(nodes, node, predicate);
674     }
675 
676     /**
677      * Get all children (by recursion) using a Predicate.
678      * // TODO this method should really be private or renamed
679      */
680     public static Iterable<Node> collectAllChildren(List<Node> nodes, Node parent, Predicate predicate) throws RepositoryException {
681         // get filtered sub nodes first
682         nodes.addAll(asList(getNodes(parent, predicate)));
683 
684         // get all children to find recursively
685         Iterator<Node> allChildren = getNodes(parent, EXCLUDE_META_DATA_FILTER).iterator();
686 
687         // recursion
688         while (allChildren.hasNext()) {
689             collectAllChildren(nodes, allChildren.next(), predicate);
690         }
691 
692         return nodes;
693     }
694 
695     /**
696      * Get all Ancestors until level 1.
697      */
698     public static Collection<Node> getAncestors(Node node) throws RepositoryException {
699         List<Node> allAncestors = new ArrayList<Node>();
700         int level = node.getDepth();
701         while (level != 0) {
702             try {
703                 allAncestors.add((Node) node.getAncestor(--level));
704             } catch (AccessDeniedException e) {
705                 log.debug("Node " + node.getIdentifier() + " didn't allow access to Ancestor's ");
706             }
707         }
708         return allAncestors;
709     }
710 
711     /**
712      * Used for building exception messages where we want to avoid handling another exception inside a throws clause.
713      */
714     public static String getNodeIdentifierIfPossible(Node content) {
715         try {
716             return content.getIdentifier();
717         } catch (RepositoryException e) {
718             return "<not available>";
719         }
720     }
721 
722     public static String getNodePathIfPossible(Node node) {
723         try {
724             return node.getPath();
725         } catch (RepositoryException e) {
726             return "<not available>";
727         }
728     }
729 
730     /**
731      * Return the Path of the node.
732      *
733      * @return the path for the node or an empty String in case of exception
734      */
735     public static String getPathIfPossible(Node node) {
736         try {
737             return node.getPath();
738         } catch (RepositoryException e) {
739             log.error("Failed to get handle: " + e.getMessage(), e);
740             return StringUtils.EMPTY;
741         }
742     }
743 
744     public static NodeIterator filterNodeType(NodeIterator iterator, String nodeType){
745         return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.NodeTypePredicate(nodeType));
746     }
747 
748     public static NodeIterator filterDuplicates(NodeIterator iterator){
749         return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.DuplicateNodePredicate());
750     }
751 
752     public static NodeIterator filterParentNodeType(NodeIterator iterator, final String nodeType) throws RepositoryException{
753         return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.NodeTypeParentPredicate(nodeType)) {
754             @Override
755             public Node nextNode(){
756                 Node node = super.nextNode();
757                 try {
758                     while(node.getDepth() != 0 && !node.isNodeType(nodeType)){
759                         if(node.getDepth() != 0){
760                             node = node.getParent();
761                         }
762                     }
763                 } catch (RepositoryException e) {
764                     throw new RuntimeException(e.getMessage(), e);
765                 }
766                 return node;
767             }
768         };
769     }
770 
771     /**
772      * Filters rows of result set from query by provided selector and returns such nodes in the NodeIterator. Example of such query would be
773      * <i>SELECT page.* FROM [mgnl:page] AS page WHERE page.title like 'something%'</i>. Selector in this query is <i>page</i>
774      *
775      * @param iterator RowIterator from the result set of executed query
776      * @param selector Selector used in the query to identify nodes
777      * @return NodeIterator containing only nodes identified by the selector
778      */
779     public static NodeIterator filterParentNodeType(RowIterator iterator, final String selector) throws RepositoryException {
780         return new NodeIteratorAdapter(iterator) {
781             @Override
782             public Node nextNode() throws NoSuchElementException {
783                 Row row = (Row) next();
784                 try {
785                     return row.getNode(selector);
786                 } catch (RepositoryException e) {
787                     // no way to pass a root cause to NSEE
788                     log.debug(e.getMessage(), e);
789                     throw new NoSuchElementException(e.getMessage());
790                 }
791             }
792         };
793     }
794 
795     public static Collection<Node> getCollectionFromNodeIterator(NodeIterator iterator){
796         Collection<Node> nodeCollection = new LinkedHashSet<Node>(150);
797         while(iterator.hasNext()){
798             nodeCollection.add(iterator.nextNode());
799         }
800         return nodeCollection;
801     }
802 
803     public static Node getSameNameSiblingNode(Node siblingNode) throws RepositoryException {
804         Node parentNode = siblingNode.getParent();
805         long sameNameSiblingNumber = parentNode.getNodes(siblingNode.getName()).getSize();
806         if (sameNameSiblingNumber > 1) {
807             return siblingNode;
808         }
809         return findSameNameSiblingNode(siblingNode);
810     }
811 
812     private static Node findSameNameSiblingNode(Node rootNode) throws RepositoryException {
813         Set<String> names = new HashSet<String>();
814         Queue<NodeIterator> queue = new LinkedList<NodeIterator>();
815         queue.add(rootNode.getNodes());
816         while (!queue.isEmpty()) {
817             NodeIterator children = queue.poll();
818             while (children.hasNext()) {
819                 Node child = children.nextNode();
820                 if (names.contains(child.getName())) {
821                     return child;
822                 }
823                 names.add(child.getName());
824                 queue.add(child.getNodes());
825             }
826             names.clear();
827         }
828 
829         return null;
830     }
831 }