View Javadoc

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