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