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.exception.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) throws RepositoryException {
189         if (node instanceof DelegateNodeWrapper)
190             return ((DelegateNodeWrapper) node).deepUnwrap(wrapper);
191         return node;
192     }
193 
194     /**
195      * Convenience - delegate to {@link Node#orderBefore(String, String)}.
196      */
197     public static void orderBefore(Node node, String siblingName) throws RepositoryException {
198         node.getParent().orderBefore(node.getName(), siblingName);
199     }
200 
201     /**
202      * Orders the node directly after a given sibling. If no sibling is specified the node is placed first.
203      */
204     public static void orderAfter(Node node, String siblingName) throws RepositoryException {
205 
206         if (siblingName == null) {
207             orderFirst(node);
208             return;
209         }
210 
211         Node parent = node.getParent();
212         Node sibling = parent.getNode(siblingName);
213         Node siblingAfter = getSiblingAfter(sibling);
214 
215         if (siblingAfter == null) {
216             orderLast(node);
217             return;
218         }
219 
220         // Move the node before the sibling directly after the target sibling
221         parent.orderBefore(node.getName(), siblingAfter.getName());
222     }
223 
224     /**
225      * Orders the node first among its siblings.
226      */
227     public static void orderFirst(Node node) throws RepositoryException {
228         Node parent = node.getParent();
229         NodeIterator siblings = parent.getNodes();
230         Node firstSibling = siblings.nextNode();
231         if (!firstSibling.isSame(node)) {
232             parent.orderBefore(node.getName(), firstSibling.getName());
233         }
234     }
235 
236     /**
237      * Orders the node last among its siblings.
238      */
239     public static void orderLast(Node node) throws RepositoryException {
240         node.getParent().orderBefore(node.getName(), null);
241     }
242 
243     /**
244      * Orders the node up one step among its siblings. If the node is the only sibling or the first sibling this method
245      * has no effect.
246      */
247     public static void orderNodeUp(Node node) throws RepositoryException {
248         Node siblingBefore = getSiblingBefore(node);
249         if (siblingBefore != null) {
250             node.getParent().orderBefore(node.getName(), siblingBefore.getName());
251         }
252     }
253 
254     /**
255      * Orders the node down one step among its siblings. If the node is the only sibling or the last sibling this method
256      * has no effect.
257      */
258     public static void orderNodeDown(Node node) throws RepositoryException {
259         Node siblingAfter = getSiblingAfter(node);
260         if (siblingAfter != null) {
261             node.getParent().orderBefore(siblingAfter.getName(), node.getName());
262         }
263     }
264 
265     public static Node getSiblingBefore(Node node) throws RepositoryException {
266         Node parent = node.getParent();
267         NodeIterator siblings = parent.getNodes();
268         Node previousSibling = null;
269         while (siblings.hasNext()) {
270             Node sibling = siblings.nextNode();
271             if (isSame(node, sibling)) {
272                 return previousSibling;
273             }
274             previousSibling = sibling;
275         }
276         return null;
277     }
278 
279     public static Node getSiblingAfter(Node node) throws RepositoryException {
280         Node parent = node.getParent();
281         NodeIterator siblings = parent.getNodes();
282         while (siblings.hasNext()) {
283             Node sibling = siblings.nextNode();
284             if (isSame(node, sibling)) {
285                 break;
286             }
287         }
288         return siblings.hasNext() ? siblings.nextNode() : null;
289     }
290 
291     public static void moveNode(Node nodeToMove, Node newParent) throws RepositoryException {
292         if (!isSame(newParent, nodeToMove.getParent())) {
293             String newPath = combinePathAndName(newParent.getPath(), nodeToMove.getName());
294             nodeToMove.getSession().move(nodeToMove.getPath(), newPath);
295         }
296     }
297 
298     public static void moveNodeBefore(Node nodeToMove, Node target) throws RepositoryException {
299         Node targetParent = target.getParent();
300         moveNode(nodeToMove, targetParent);
301         targetParent.orderBefore(nodeToMove.getName(), target.getName());
302     }
303 
304     public static void moveNodeAfter(Node nodeToMove, Node target) throws RepositoryException {
305         Node targetParent = target.getParent();
306         moveNode(nodeToMove, targetParent);
307         orderAfter(nodeToMove, target.getName());
308     }
309 
310     public static boolean isFirstSibling(Node node) throws RepositoryException {
311         Node parent = node.getParent();
312         NodeIterator nodes = parent.getNodes();
313         return isSame(nodes.nextNode(), node);
314     }
315 
316     public static boolean isLastSibling(Node node) throws RepositoryException {
317         Node parent = node.getParent();
318         NodeIterator nodes = parent.getNodes();
319         Node last = null;
320         while (nodes.hasNext()) {
321             last = nodes.nextNode();
322         }
323         return isSame(last, node);
324     }
325 
326     public static void renameNode(Node node, String newName) throws RepositoryException {
327         Node parent = node.getParent();
328         String newPath = combinePathAndName(parent.getPath(), newName);
329         node.getSession().move(node.getPath(), newPath);
330     }
331 
332     /**
333      * @return Whether the provided node as the provided permission or not.
334      * @throws RuntimeException
335      *             in case of RepositoryException.
336      */
337     public static boolean isGranted(Node node, long permissions) {
338         try {
339             return PermissionUtil.isGranted(node.getSession(), node.getPath(),
340                     PermissionUtil.convertPermissions(permissions));
341         } catch (RepositoryException e) {
342             // TODO dlipp - apply consistent ExceptionHandling
343             throw new RuntimeException(e);
344         }
345     }
346 
347     /**
348      * Returns true if both arguments represents the same node. In case the nodes are wrapped the comparison is done one
349      * the actual nodes behind the wrappers.
350      */
351     private static boolean isSame(Node lhs, Node rhs) throws RepositoryException {
352         return unwrap(lhs).isSame(unwrap(rhs));
353     }
354 
355     private static String combinePathAndName(String path, String name) {
356         if ("/".equals(path)) {
357             return "/" + name;
358         }
359         return path + "/" + name;
360     }
361 
362     /**
363      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
364      * method will simply return it.
365      */
366     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName) throws RepositoryException, PathNotFoundException, AccessDeniedException {
367         return createPath(parent, relPath, primaryNodeTypeName, false);
368     }
369 
370     /**
371      * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the
372      * method will simply return it.
373      */
374     public static Node createPath(Node parent, String relPath, String primaryNodeTypeName, boolean save) throws RepositoryException, PathNotFoundException, AccessDeniedException {
375         // remove leading /
376         String currentPath = StringUtils.removeStart(relPath, "/");
377 
378         if (StringUtils.isEmpty(currentPath)) {
379             return parent;
380         }
381 
382         Node root = parent;
383         String[] names = currentPath.split("/");
384 
385         for (int i = 0; i < names.length; i++) {
386             String name = names[i];
387             if (root.hasNode(name)) {
388                 root = root.getNode(name);
389             } else {
390                 final Node newNode = root.addNode(name, primaryNodeTypeName);
391                 if (save) {
392                     root.getSession().save();
393                 }
394                 root = newNode;
395             }
396         }
397         return root;
398     }
399 
400     /**
401      * Visits the given node and then all of nodes beneath it except for metadata nodes and nodes of jcr type.
402      */
403     public static void visit(Node node, NodeVisitor visitor) throws RepositoryException {
404         visit(node, visitor, EXCLUDE_META_DATA_FILTER);
405     }
406 
407     public static void visit(Node node, NodeVisitor visitor, Predicate predicate) throws RepositoryException {
408         // TODO should it really visit the start node even if it doesn't match the filter?
409         visitor.visit(node);
410         for (Node child : getNodes(node, predicate)) {
411             visit(child, visitor, predicate);
412         }
413         if (visitor instanceof PostNodeVisitor) {
414             ((PostNodeVisitor) visitor).postVisit(node);
415         }
416     }
417 
418     public static Iterable<Node> getNodes(Node parent, Predicate predicate) throws RepositoryException {
419         return asIterable(new FilteringNodeIterator(parent.getNodes(), predicate));
420     }
421 
422     public static Iterable<Node> getNodes(Node parent) throws RepositoryException {
423         return getNodes(parent, EXCLUDE_META_DATA_FILTER);
424     }
425 
426     public static Iterable<Node> getNodes(Node parent, String nodeTypeName) throws RepositoryException {
427         return getNodes(parent, new NodeTypePredicate(nodeTypeName, false));
428     }
429 
430     public static Iterable<Node> asIterable(NodeIterator iterator) {
431         return new NodeIterableAdapter(iterator);
432     }
433 
434     public static List<Node> asList(Iterable<Node> nodes) {
435         List<Node> nodesList = new ArrayList<Node>();
436         for (Node node : nodes) {
437             nodesList.add(node);
438         }
439         return nodesList;
440     }
441 
442     /**
443      * This method return the node's name on success, otherwise it handles the {@link RepositoryException} by throwing a
444      * {@link RuntimeRepositoryException}.
445      */
446     public static String getName(Node content) {
447         try {
448             return content.getName();
449         } catch (RepositoryException e) {
450             throw new RuntimeRepositoryException(e);
451         }
452     }
453 
454     /**
455      * Get all children (by recursion) using MAGNOLIA_FILTER (filter accepting all nodes of a type with namespace mgnl).
456      */
457     public static Iterable<Node> collectAllChildren(Node node) throws RepositoryException {
458         List<Node> nodes = new ArrayList<Node>();
459         return collectAllChildren(nodes, node, MAGNOLIA_FILTER);
460     }
461 
462     /**
463      * Get all children (by recursion) using a Predicate.
464      */
465     public static Iterable<Node> collectAllChildren(Node node, Predicate predicate) throws RepositoryException {
466         List<Node> nodes = new ArrayList<Node>();
467         return collectAllChildren(nodes, node, predicate);
468     }
469 
470     /**
471      * Get all children (by recursion) using a Predicate.
472      * // TODO this method should really be private or renamed
473      */
474     public static Iterable<Node> collectAllChildren(List<Node> nodes, Node parent, Predicate predicate) throws RepositoryException {
475         // get filtered sub nodes first
476         nodes.addAll(asList(getNodes(parent, predicate)));
477 
478         // get all children to find recursively
479         Iterator<Node> allChildren = getNodes(parent, EXCLUDE_META_DATA_FILTER).iterator();
480 
481         // recursion
482         while (allChildren.hasNext()) {
483             collectAllChildren(nodes, allChildren.next(), predicate);
484         }
485 
486         return nodes;
487     }
488 
489     /**
490      * Get all Ancestors until level 1.
491      */
492     public static Collection<Node> getAncestors(Node node) throws RepositoryException {
493         List<Node> allAncestors = new ArrayList<Node>();
494         int level = node.getDepth();
495         while (level != 0) {
496             try {
497                 allAncestors.add((Node) node.getAncestor(--level));
498             } catch (AccessDeniedException e) {
499                 log.debug("Node " + node.getIdentifier() + " didn't allow access to Ancestor's ");
500             }
501         }
502         return allAncestors;
503     }
504 
505     /**
506      * Used for building exception messages where we want to avoid handling another exception inside a throws clause.
507      */
508     public static String getNodeIdentifierIfPossible(Node content) {
509         try {
510             return content.getIdentifier();
511         } catch (RepositoryException e) {
512             return "<not available>";
513         }
514     }
515 
516     public static String getNodePathIfPossible(Node node) {
517         try {
518             return node.getPath();
519         } catch (RepositoryException e) {
520             return "<not available>";
521         }
522     }
523 
524     /**
525      * Return the Path of the node.
526      *
527      * @return the path for the node or an empty String in case of exception
528      */
529     public static String getPathIfPossible(Node node) {
530         try {
531             return node.getPath();
532         } catch (RepositoryException e) {
533             log.error("Failed to get handle: " + e.getMessage(), e);
534             return StringUtils.EMPTY;
535         }
536     }
537 
538 }