View Javadoc

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