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