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