View Javadoc

1   /**
2    * This file Copyright (c) 2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.jcr.util;
35  
36  import info.magnolia.cms.core.MgnlNodeType;
37  import info.magnolia.cms.security.AccessDeniedException;
38  import info.magnolia.cms.security.PermissionUtil;
39  import info.magnolia.context.MgnlContext;
40  import info.magnolia.jcr.RuntimeRepositoryException;
41  import info.magnolia.jcr.iterator.NodeIterableAdapter;
42  import info.magnolia.jcr.predicate.AbstractPredicate;
43  import info.magnolia.jcr.wrapper.DelegateNodeWrapper;
44  import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper;
45  
46  import java.util.ArrayList;
47  import java.util.Collection;
48  import java.util.Iterator;
49  import java.util.List;
50  
51  import javax.jcr.Node;
52  import javax.jcr.NodeIterator;
53  import javax.jcr.PathNotFoundException;
54  import javax.jcr.Property;
55  import javax.jcr.RepositoryException;
56  import javax.jcr.Session;
57  import javax.jcr.nodetype.NodeType;
58  
59  import org.apache.commons.lang.StringUtils;
60  import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
61  import org.apache.jackrabbit.commons.predicate.NodeTypePredicate;
62  import org.apache.jackrabbit.commons.predicate.Predicate;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  /**
67   * Various utility methods to collect data from JCR repository.
68   *
69   * @version $Id$
70   */
71  public class NodeUtil {
72  
73      private static final Logger log = LoggerFactory.getLogger(NodeUtil.class);
74  
75      /**
76       * Node filter accepting everything except nodes with namespace jcr (version and system store).
77       */
78      public static Predicate ALL_NODES_EXCEPT_JCR_FILTER = new AbstractPredicate<Node>() {
79          @Override
80          public boolean evaluateTyped(Node node) {
81              try {
82                  return !node.getName().startsWith(MgnlNodeType.JCR_PREFIX);
83              } catch (RepositoryException e) {
84                  return false;
85              }
86          }
87      };
88  
89      /**
90       * Node filter accepting everything except meta data and jcr types.
91       */
92      public static AbstractPredicate<Node> EXCLUDE_META_DATA_FILTER = new AbstractPredicate<Node>() {
93  
94          @Override
95          public boolean evaluateTyped(Node node) {
96              try {
97                  return !node.getName().startsWith(MgnlNodeType.JCR_PREFIX)
98                  && !NodeUtil.isNodeType(node, MgnlNodeType.NT_METADATA);
99              } catch (RepositoryException e) {
100                 return false;
101             }
102         }
103     };
104 
105     /**
106      * Node filter accepting all nodes of a type with namespace mgnl.
107      */
108     public static AbstractPredicate<Node> MAGNOLIA_FILTER = new AbstractPredicate<Node>() {
109 
110         @Override
111         public boolean evaluateTyped(Node node) {
112 
113             try {
114                 String nodeTypeName = node.getPrimaryNodeType().getName();
115                 // accept only "magnolia" nodes
116                 return nodeTypeName.startsWith(MgnlNodeType.MGNL_PREFIX);
117             } catch (RepositoryException e) {
118                 // TODO should we really mask this error? shouldn't it be thrown instead?
119                 log.error("Unable to read nodetype for node {}", getNodePathIfPossible(node));
120             }
121             return false;
122         }
123     };
124 
125     /**
126      * Get a Node by identifier.
127      */
128     public static Node getNodeByIdentifier(String workspace, String identifier) throws RepositoryException {
129         Node target = null;
130         Session jcrSession;
131         if (workspace == null || identifier == null) {
132             return target;
133         }
134 
135         jcrSession = MgnlContext.getJCRSession(workspace);
136         if (jcrSession != null) {
137             target = jcrSession.getNodeByIdentifier(identifier);
138         }
139         return target;
140     }
141 
142     /**
143      * from default content.
144      */
145     public static boolean hasMixin(Node node, String mixinName) throws RepositoryException {
146         if (StringUtils.isBlank(mixinName)) {
147             throw new IllegalArgumentException("Mixin name can't be empty.");
148         }
149         for (NodeType type : node.getMixinNodeTypes()) {
150             if (mixinName.equals(type.getName())) {
151                 return true;
152             }
153         }
154         return false;
155     }
156 
157     /**
158      * TODO dlipp: better name? Clear javadoc! Move to MetaDataUtil, do not assign method-param! TODO cringele :
159      * shouldn't @param nodeType be aligned to JCR API? There it is nodeTypeName, nodeType is used for NodeType object
160      */
161     public static boolean isNodeType(Node node, String type) throws RepositoryException {
162         node = NodeUtil.deepUnwrap(node, JCRPropertiesFilteringNodeWrapper.class);
163         final String actualType = node.getProperty(MgnlNodeType.JCR_PRIMARY_TYPE).getString();
164         // if the node is frozen, and we're not looking specifically for frozen nodes, then we compare with the original
165         // node type
166         if (MgnlNodeType.NT_FROZENNODE.equals(actualType) && !(MgnlNodeType.NT_FROZENNODE.equals(type))) {
167             final Property p = node.getProperty(MgnlNodeType.JCR_FROZEN_PRIMARY_TYPE);
168             final String s = p.getString();
169             return s.equalsIgnoreCase(type);
170 
171             // FIXME this method does not consider mixins when the node is frozen
172         }
173         return node.isNodeType(type);
174     }
175 
176     public static Node unwrap(Node node) throws RepositoryException {
177         Node unwrappedNode = node;
178         while (unwrappedNode instanceof DelegateNodeWrapper) {
179             unwrappedNode = ((DelegateNodeWrapper) unwrappedNode).getWrappedNode();
180         }
181         return unwrappedNode;
182     }
183 
184     /**
185      * Removes a wrapper by type. The wrapper can be deep in a chain of wrappers in which case wrappers before it will
186      * be cloned creating a new chain that leads to the same real node.
187      */
188     public static Node deepUnwrap(Node node, Class<? extends DelegateNodeWrapper> wrapper) {
189         if (node instanceof DelegateNodeWrapper) {
190             return ((DelegateNodeWrapper) node).deepUnwrap(wrapper);
191         }
192         return node;
193     }
194 
195     /**
196      * Removes all wrappers of a given type. Other wrappers are cloned creating a new chain that leads to the same real
197      * node.
198      */
199     public static Node deepUnwrapAll(Node node, Class<? extends DelegateNodeWrapper> wrapperClass) {
200         while (node instanceof DelegateNodeWrapper) {
201             Node unwrapped = ((DelegateNodeWrapper) node).deepUnwrap(wrapperClass);
202             // If the unwrapping had no effect we're done
203             if (unwrapped == node) {
204                 break;
205             }
206             node = unwrapped;
207         }
208         return node;
209     }
210 
211     public static boolean isWrappedWith(Node node, Class<? extends DelegateNodeWrapper> wrapper) {
212         if (wrapper.isInstance(node)){
213             return true;
214         }
215 
216         if (node instanceof DelegateNodeWrapper) {
217             return isWrappedWith(((DelegateNodeWrapper)node).getWrappedNode(), wrapper);
218         }
219         return false;
220     }
221 
222 
223     /**
224      * Convenience - delegate to {@link Node#orderBefore(String, String)}.
225      */
226     public static void orderBefore(Node node, String siblingName) throws RepositoryException {
227         node.getParent().orderBefore(node.getName(), siblingName);
228     }
229 
230     /**
231      * Orders the node directly after a given sibling. If no sibling is specified the node is placed first.
232      */
233     public static void orderAfter(Node node, String siblingName) throws RepositoryException {
234 
235         if (siblingName == null) {
236             orderFirst(node);
237             return;
238         }
239 
240         Node parent = node.getParent();
241         Node sibling = parent.getNode(siblingName);
242         Node siblingAfter = getSiblingAfter(sibling);
243 
244         if (siblingAfter == null) {
245             orderLast(node);
246             return;
247         }
248 
249         // Move the node before the sibling directly after the target sibling
250         parent.orderBefore(node.getName(), siblingAfter.getName());
251     }
252 
253     /**
254      * Orders the node first among its siblings.
255      */
256     public static void orderFirst(Node node) throws RepositoryException {
257         Node parent = node.getParent();
258         NodeIterator siblings = parent.getNodes();
259         Node firstSibling = siblings.nextNode();
260         if (!firstSibling.isSame(node)) {
261             parent.orderBefore(node.getName(), firstSibling.getName());
262         }
263     }
264 
265     /**
266      * Orders the node last among its siblings.
267      */
268     public static void orderLast(Node node) throws RepositoryException {
269         node.getParent().orderBefore(node.getName(), null);
270     }
271 
272     /**
273      * Orders the node up one step among its siblings. If the node is the only sibling or the first sibling this method
274      * has no effect.
275      */
276     public static void orderNodeUp(Node node) throws RepositoryException {
277         Node siblingBefore = getSiblingBefore(node);
278         if (siblingBefore != null) {
279             node.getParent().orderBefore(node.getName(), siblingBefore.getName());
280         }
281     }
282 
283     /**
284      * Orders the node down one step among its siblings. If the node is the only sibling or the last sibling this method
285      * has no effect.
286      */
287     public static void orderNodeDown(Node node) throws RepositoryException {
288         Node siblingAfter = getSiblingAfter(node);
289         if (siblingAfter != null) {
290             node.getParent().orderBefore(siblingAfter.getName(), node.getName());
291         }
292     }
293 
294     public static Node getSiblingBefore(Node node) throws RepositoryException {
295         Node parent = node.getParent();
296         NodeIterator siblings = parent.getNodes();
297         Node previousSibling = null;
298         while (siblings.hasNext()) {
299             Node sibling = siblings.nextNode();
300             if (isSame(node, sibling)) {
301                 return previousSibling;
302             }
303             previousSibling = sibling;
304         }
305         return null;
306     }
307 
308     public static Node getSiblingAfter(Node node) throws RepositoryException {
309         Node parent = node.getParent();
310         NodeIterator siblings = parent.getNodes();
311         while (siblings.hasNext()) {
312             Node sibling = siblings.nextNode();
313             if (isSame(node, sibling)) {
314                 break;
315             }
316         }
317         return siblings.hasNext() ? siblings.nextNode() : null;
318     }
319 
320     public static void moveNode(Node nodeToMove, Node newParent) throws RepositoryException {
321         if (!isSame(newParent, nodeToMove.getParent())) {
322             String newPath = combinePathAndName(newParent.getPath(), nodeToMove.getName());
323             nodeToMove.getSession().move(nodeToMove.getPath(), newPath);
324         }
325     }
326 
327     public static void moveNodeBefore(Node nodeToMove, Node target) throws RepositoryException {
328         Node targetParent = target.getParent();
329         moveNode(nodeToMove, targetParent);
330         targetParent.orderBefore(nodeToMove.getName(), target.getName());
331     }
332 
333     public static void moveNodeAfter(Node nodeToMove, Node target) throws RepositoryException {
334         Node targetParent = target.getParent();
335         moveNode(nodeToMove, targetParent);
336         orderAfter(nodeToMove, target.getName());
337     }
338 
339     public static boolean isFirstSibling(Node node) throws RepositoryException {
340         Node parent = node.getParent();
341         NodeIterator nodes = parent.getNodes();
342         return isSame(nodes.nextNode(), node);
343     }
344 
345     /**
346      * Check if node1 and node2 are siblings.
347      */
348     public static boolean isSameNameSiblings(Node node1, Node node2) throws RepositoryException {
349         Node parent1 = node1.getParent();
350         Node parent2 = node2.getParent();
351         return isSame(parent1, parent2) && node1.getName().equals(node2.getName());
352     }
353 
354     public static boolean isLastSibling(Node node) throws RepositoryException {
355         Node parent = node.getParent();
356         NodeIterator nodes = parent.getNodes();
357         Node last = null;
358         while (nodes.hasNext()) {
359             last = nodes.nextNode();
360         }
361         return isSame(last, node);
362     }
363 
364     public static void renameNode(Node node, String newName) throws RepositoryException {
365         Node parent = node.getParent();
366         String newPath = combinePathAndName(parent.getPath(), newName);
367         node.getSession().move(node.getPath(), newPath);
368     }
369 
370 
371     /**
372      * @return Whether the provided node as the provided permission or not.
373      * @throws RuntimeException
374      *             in case of RepositoryException.
375      */
376     public static boolean isGranted(Node node, long permissions) {
377         try {
378             return PermissionUtil.isGranted(node, permissions);
379         } catch (RepositoryException e) {
380             // TODO dlipp - apply consistent ExceptionHandling
381             throw new RuntimeException(e);
382         }
383     }
384 
385     /**
386      * Returns true if both arguments represents the same node. In case the nodes are wrapped the comparison is done one
387      * the actual nodes behind the wrappers.
388      */
389     public static boolean isSame(Node lhs, Node rhs) throws RepositoryException {
390         return unwrap(lhs).isSame(unwrap(rhs));
391     }
392 
393     public static String combinePathAndName(String path, String name) {
394         if ("/".equals(path)) {
395             return "/" + name;
396         }
397         return path + "/" + name;
398     }
399 
400     /**
401      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
402      * method will simply return it.
403      */
404     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName) throws RepositoryException, PathNotFoundException, AccessDeniedException {
405         return createPath(parent, relPath, primaryNodeTypeName, false);
406     }
407 
408     /**
409      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
410      * method will simply return it.
411      */
412     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName, boolean save) throws RepositoryException, PathNotFoundException, AccessDeniedException {
413         // remove leading /
414         String currentPath = StringUtils.removeStart(relPath, "/");
415 
416         if (StringUtils.isEmpty(currentPath)) {
417             return parent;
418         }
419 
420         Node root = parent;
421         String[] names = currentPath.split("/");
422 
423         for (int i = 0; i < names.length; i++) {
424             String name = names[i];
425             if (root.hasNode(name)) {
426                 root = root.getNode(name);
427             } else {
428                 final Node newNode = root.addNode(name, primaryNodeTypeName);
429                 if (save) {
430                     root.getSession().save();
431                 }
432                 root = newNode;
433             }
434         }
435         return root;
436     }
437 
438     /**
439      * Visits the given node and then all of nodes beneath it except for metadata nodes and nodes of jcr type.
440      */
441     public static void visit(Node node, NodeVisitor visitor) throws RepositoryException {
442         visit(node, visitor, EXCLUDE_META_DATA_FILTER);
443     }
444 
445     public static void visit(Node node, NodeVisitor visitor, Predicate predicate) throws RepositoryException {
446         // TODO should it really visit the start node even if it doesn't match the filter?
447         visitor.visit(node);
448         for (Node child : getNodes(node, predicate)) {
449             visit(child, visitor, predicate);
450         }
451         if (visitor instanceof PostNodeVisitor) {
452             ((PostNodeVisitor) visitor).postVisit(node);
453         }
454     }
455 
456     public static Iterable<Node> getNodes(Node parent, Predicate predicate) throws RepositoryException {
457         return asIterable(new FilteringNodeIterator(parent.getNodes(), predicate));
458     }
459 
460     public static Iterable<Node> getNodes(Node parent) throws RepositoryException {
461         return getNodes(parent, EXCLUDE_META_DATA_FILTER);
462     }
463 
464     public static Iterable<Node> getNodes(Node parent, String nodeTypeName) throws RepositoryException {
465         return getNodes(parent, new NodeTypePredicate(nodeTypeName, false));
466     }
467 
468     public static Iterable<Node> asIterable(NodeIterator iterator) {
469         return new NodeIterableAdapter(iterator);
470     }
471 
472     public static List<Node> asList(Iterable<Node> nodes) {
473         List<Node> nodesList = new ArrayList<Node>();
474         for (Node node : nodes) {
475             nodesList.add(node);
476         }
477         return nodesList;
478     }
479 
480     /**
481      * This method return the node's name on success, otherwise it handles the {@link RepositoryException} by throwing a
482      * {@link RuntimeRepositoryException}.
483      */
484     public static String getName(Node content) {
485         try {
486             return content.getName();
487         } catch (RepositoryException e) {
488             throw new RuntimeRepositoryException(e);
489         }
490     }
491 
492     /**
493      * Get all children (by recursion) using MAGNOLIA_FILTER (filter accepting all nodes of a type with namespace mgnl).
494      */
495     public static Iterable<Node> collectAllChildren(Node node) throws RepositoryException {
496         List<Node> nodes = new ArrayList<Node>();
497         return collectAllChildren(nodes, node, MAGNOLIA_FILTER);
498     }
499 
500     /**
501      * Get all children (by recursion) using a Predicate.
502      */
503     public static Iterable<Node> collectAllChildren(Node node, Predicate predicate) throws RepositoryException {
504         List<Node> nodes = new ArrayList<Node>();
505         return collectAllChildren(nodes, node, predicate);
506     }
507 
508     /**
509      * Get all children (by recursion) using a Predicate.
510      * // TODO this method should really be private or renamed
511      */
512     public static Iterable<Node> collectAllChildren(List<Node> nodes, Node parent, Predicate predicate) throws RepositoryException {
513         // get filtered sub nodes first
514         nodes.addAll(asList(getNodes(parent, predicate)));
515 
516         // get all children to find recursively
517         Iterator<Node> allChildren = getNodes(parent, EXCLUDE_META_DATA_FILTER).iterator();
518 
519         // recursion
520         while (allChildren.hasNext()) {
521             collectAllChildren(nodes, allChildren.next(), predicate);
522         }
523 
524         return nodes;
525     }
526 
527     /**
528      * Get all Ancestors until level 1.
529      */
530     public static Collection<Node> getAncestors(Node node) throws RepositoryException {
531         List<Node> allAncestors = new ArrayList<Node>();
532         int level = node.getDepth();
533         while (level != 0) {
534             try {
535                 allAncestors.add((Node) node.getAncestor(--level));
536             } catch (AccessDeniedException e) {
537                 log.debug("Node " + node.getIdentifier() + " didn't allow access to Ancestor's ");
538             }
539         }
540         return allAncestors;
541     }
542 
543     /**
544      * Used for building exception messages where we want to avoid handling another exception inside a throws clause.
545      */
546     public static String getNodeIdentifierIfPossible(Node content) {
547         try {
548             return content.getIdentifier();
549         } catch (RepositoryException e) {
550             return "<not available>";
551         }
552     }
553 
554     public static String getNodePathIfPossible(Node node) {
555         try {
556             return node.getPath();
557         } catch (RepositoryException e) {
558             return "<not available>";
559         }
560     }
561 
562     /**
563      * Return the Path of the node.
564      *
565      * @return the path for the node or an empty String in case of exception
566      */
567     public static String getPathIfPossible(Node node) {
568         try {
569             return node.getPath();
570         } catch (RepositoryException e) {
571             log.error("Failed to get handle: " + e.getMessage(), e);
572             return StringUtils.EMPTY;
573         }
574     }
575 
576 }