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