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