/** * This file Copyright (c) 2011-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.jcr.util; import info.magnolia.cms.security.AccessDeniedException; import info.magnolia.cms.security.PermissionUtil; import info.magnolia.context.MgnlContext; import info.magnolia.jcr.RuntimeRepositoryException; import info.magnolia.jcr.iterator.NodeIterableAdapter; import info.magnolia.jcr.predicate.AbstractPredicate; import info.magnolia.jcr.wrapper.DelegateNodeWrapper; import info.magnolia.jcr.wrapper.JCRPropertiesFilteringNodeWrapper; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeManager; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator; import org.apache.jackrabbit.commons.predicate.NodeTypePredicate; import org.apache.jackrabbit.commons.predicate.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Various utility methods to collect data from JCR repository. */ public class NodeUtil { private static final Logger log = LoggerFactory.getLogger(NodeUtil.class); /** * Predicate hiding properties prefixed with jcr or mgnl. * @deprecated since 5.0 - obsolete as there's no nodetypes with namespace jcr. In addition you could use {@link info.magnolia.jcr.predicate.JCRMgnlPropertyHidingPredicate} */ public static AbstractPredicate<Property> ALL_PROPERTIES_EXCEPT_JCR_AND_MGNL_FILTER = new AbstractPredicate<Property>() { @Override public boolean evaluateTyped(Property property) { try { String name = property.getName(); return !name.startsWith(NodeTypes.JCR_PREFIX) && !name.startsWith(NodeTypes.MGNL_PREFIX); } catch (RepositoryException e) { String path; try { path = property.getPath(); } catch (RepositoryException e1) { path = "<path not available>"; } log.error("Unable to read name of property {}", path); // either invalid or not accessible to the current user return false; } } }; /** * Node filter accepting everything except nodes with namespace jcr (version and system store). * @deprecated since 5.0 - obsolete as there's no nodetypes with namespace jcr */ public static Predicate ALL_NODES_EXCEPT_JCR_FILTER = new AbstractPredicate<Node>() { @Override public boolean evaluateTyped(Node node) { try { return !node.getName().startsWith(NodeTypes.JCR_PREFIX); } catch (RepositoryException e) { log.error("Unable to read name for node {}", getNodePathIfPossible(node)); return false; } } }; /** * Node filter accepting everything except meta data and jcr types. * @deprecated since 5.0 - obsolete as there's no nodetypes with namespace jcr and because of MAGNOLIA-4640 */ public static AbstractPredicate<Node> EXCLUDE_META_DATA_FILTER = new AbstractPredicate<Node>() { @Override public boolean evaluateTyped(Node node) { try { return !node.getName().startsWith(NodeTypes.JCR_PREFIX) && !NodeUtil.isNodeType(node, NodeTypes.MetaData.NAME); } catch (RepositoryException e) { log.error("Unable to read name or nodeType for node {}", getNodePathIfPossible(node)); return false; } } }; /** * Node filter accepting all nodes of a type with namespace mgnl. */ public static AbstractPredicate<Node> MAGNOLIA_FILTER = new AbstractPredicate<Node>() { @Override public boolean evaluateTyped(Node node) { try { String nodeTypeName = node.getPrimaryNodeType().getName(); // accept only "magnolia" nodes return nodeTypeName.startsWith(NodeTypes.MGNL_PREFIX); } catch (RepositoryException e) { log.error("Unable to read nodeType for node {}", getNodePathIfPossible(node)); } return false; } }; /** * Get a Node by identifier. */ public static Node getNodeByIdentifier(String workspace, String identifier) throws RepositoryException { Node target = null; Session jcrSession; if (workspace == null || identifier == null) { return target; } jcrSession = MgnlContext.getJCRSession(workspace); if (jcrSession != null) { target = jcrSession.getNodeByIdentifier(identifier); } return target; } /** * from default content. */ public static boolean hasMixin(Node node, String mixinName) throws RepositoryException { if (StringUtils.isBlank(mixinName)) { throw new IllegalArgumentException("Mixin name can't be empty."); } for (NodeType type : node.getMixinNodeTypes()) { if (mixinName.equals(type.getName())) { return true; } } return false; } /** * TODO dlipp: better name? Clear javadoc! Do not assign method-param! * TODO cringele : shouldn't @param nodeType be aligned to JCR API? There it is nodeTypeName, nodeType is used for NodeType object */ public static boolean isNodeType(Node node, String type) throws RepositoryException { node = NodeUtil.deepUnwrap(node, JCRPropertiesFilteringNodeWrapper.class); final String actualType = node.getProperty(JcrConstants.JCR_PRIMARYTYPE).getString(); // if the node is frozen, and we're not looking specifically for frozen nodes, then we compare with the original // node type if (JcrConstants.NT_FROZENNODE.equals(actualType) && !(JcrConstants.NT_FROZENNODE.equals(type))) { final Property p = node.getProperty(JcrConstants.JCR_FROZENPRIMARYTYPE); final String s = p.getString(); NodeTypeManager ntManager = node.getSession().getWorkspace().getNodeTypeManager(); NodeType primaryNodeType = ntManager.getNodeType(s); return primaryNodeType.isNodeType(type); } return node.isNodeType(type); } public static Node unwrap(Node node) throws RepositoryException { Node unwrappedNode = node; while (unwrappedNode instanceof DelegateNodeWrapper) { unwrappedNode = ((DelegateNodeWrapper) unwrappedNode).getWrappedNode(); } return unwrappedNode; } /** * Removes a wrapper by type. The wrapper can be deep in a chain of wrappers in which case wrappers before it will * be cloned creating a new chain that leads to the same real node. */ public static Node deepUnwrap(Node node, Class<? extends DelegateNodeWrapper> wrapper) { if (node instanceof DelegateNodeWrapper) { return ((DelegateNodeWrapper) node).deepUnwrap(wrapper); } return node; } /** * Removes all wrappers of a given type. Other wrappers are cloned creating a new chain that leads to the same real * node. */ public static Node deepUnwrapAll(Node node, Class<? extends DelegateNodeWrapper> wrapperClass) { while (node instanceof DelegateNodeWrapper) { Node unwrapped = ((DelegateNodeWrapper) node).deepUnwrap(wrapperClass); // If the unwrapping had no effect we're done if (unwrapped == node) { break; } node = unwrapped; } return node; } public static boolean isWrappedWith(Node node, Class<? extends DelegateNodeWrapper> wrapper) { if (wrapper.isInstance(node)){ return true; } if (node instanceof DelegateNodeWrapper) { return isWrappedWith(((DelegateNodeWrapper)node).getWrappedNode(), wrapper); } return false; } /** * Convenience - delegate to {@link Node#orderBefore(String, String)}. */ public static void orderBefore(Node node, String siblingName) throws RepositoryException { node.getParent().orderBefore(node.getName(), siblingName); } /** * Orders the node directly after a given sibling. If no sibling is specified the node is placed first. */ public static void orderAfter(Node node, String siblingName) throws RepositoryException { if (siblingName == null) { orderFirst(node); return; } Node parent = node.getParent(); Node sibling = parent.getNode(siblingName); Node siblingAfter = getSiblingAfter(sibling); if (siblingAfter == null) { orderLast(node); return; } // Move the node before the sibling directly after the target sibling parent.orderBefore(node.getName(), siblingAfter.getName()); } /** * Orders the node first among its siblings. */ public static void orderFirst(Node node) throws RepositoryException { Node parent = node.getParent(); NodeIterator siblings = parent.getNodes(); Node firstSibling = siblings.nextNode(); if (!firstSibling.isSame(node)) { parent.orderBefore(node.getName(), firstSibling.getName()); } } /** * Orders the node last among its siblings. */ public static void orderLast(Node node) throws RepositoryException { node.getParent().orderBefore(node.getName(), null); } /** * Orders the node up one step among its siblings. If the node is the only sibling or the first sibling this method * has no effect. */ public static void orderNodeUp(Node node) throws RepositoryException { Node siblingBefore = getSiblingBefore(node); if (siblingBefore != null) { node.getParent().orderBefore(node.getName(), siblingBefore.getName()); } } /** * Orders the node down one step among its siblings. If the node is the only sibling or the last sibling this method * has no effect. */ public static void orderNodeDown(Node node) throws RepositoryException { Node siblingAfter = getSiblingAfter(node); if (siblingAfter != null) { node.getParent().orderBefore(siblingAfter.getName(), node.getName()); } } public static Node getSiblingBefore(Node node) throws RepositoryException { Node parent = node.getParent(); NodeIterator siblings = parent.getNodes(); Node previousSibling = null; while (siblings.hasNext()) { Node sibling = siblings.nextNode(); if (isSame(node, sibling)) { return previousSibling; } previousSibling = sibling; } return null; } public static Node getSiblingAfter(Node node) throws RepositoryException { Node parent = node.getParent(); NodeIterator siblings = parent.getNodes(); while (siblings.hasNext()) { Node sibling = siblings.nextNode(); if (isSame(node, sibling)) { break; } } return siblings.hasNext() ? siblings.nextNode() : null; } /** * Gets the siblings of this node. * @param node node from which will be siblings retrieved * @param nodeTypeName requested type of siblings nodes * @return list of siblings of the given Node (only the given node is excluded) */ public static Iterable<Node> getSiblings(Node node) throws RepositoryException { Node parent = node.getParent(); Iterable<Node> allSiblings = NodeUtil.getNodes(parent); List<Node> siblings = new ArrayList<Node>(); for(Node sibling: allSiblings) { if (!NodeUtil.isSame(node, sibling)) { siblings.add(sibling); } } return siblings; } /** * Gets the siblings of this node with certain type. * @param node node from which will be siblings retrieved * @param nodeTypeName requested type of siblings nodes * @return list of siblings of the given Node (the given node is excluded) */ public static Iterable<Node> getSiblings(Node node, String nodeTypeName) throws RepositoryException { Node parent = node.getParent(); Iterable<Node> allSiblings = NodeUtil.getNodes(parent, nodeTypeName); List<Node> sameTypeSiblings = new ArrayList<Node>(); for(Node sibling: allSiblings) { if (!NodeUtil.isSame(node, sibling)) { sameTypeSiblings.add(sibling); } } return sameTypeSiblings; } /** * Gets the siblings of this node according to predicate. * @param node node from which will be siblings retrieved * @param predicate predicate * @return list of siblings of the given Node (the given node is excluded) */ public static Iterable<Node> getSiblings(Node node, Predicate predicate) throws RepositoryException { Node parent = node.getParent(); Iterable<Node> allSiblings = NodeUtil.getNodes(parent, predicate); List<Node> sameTypeSiblings = new ArrayList<Node>(); for(Node sibling: allSiblings) { if (!NodeUtil.isSame(node, sibling)) { sameTypeSiblings.add(sibling); } } return sameTypeSiblings; } /** * Gets the siblings before this node. * @param node node from which will be siblings retrieved * @return list of siblings before the given Node (the given node is excluded) */ public static Iterable<Node> getSiblingsBefore(Node node) throws RepositoryException { int toIndex = 0; Node parent = node.getParent(); List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent)); for(Node sibling: allSiblings) { if (NodeUtil.isSame(node, sibling)) { break; } toIndex++; } return allSiblings.subList(0, toIndex); } /** * Gets the siblings after this node. * @param node node from which will be siblings retrieved * @return list of siblings after the given Node (the given node is excluded) */ public static Iterable<Node> getSiblingsAfter(Node node) throws RepositoryException { int fromIndex = 0; Node parent = node.getParent(); List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent)); for(Node sibling: allSiblings) { if (NodeUtil.isSame(node, sibling)) { fromIndex++; break; } fromIndex++; } return allSiblings.subList(fromIndex, allSiblings.size()); } /** * Gets the siblings before this node with certain type. * @param node node from which will be siblings retrieved * @param nodeTypeName requested type of siblings nodes * @return list of siblings before the given Node (the given node is excluded) */ public static Iterable<Node> getSiblingsBefore(Node node, String nodeTypeName) throws RepositoryException { Node parent = node.getParent(); Iterable<Node> allSiblings = NodeUtil.getNodes(parent); List<Node> sameTypeSiblings = new ArrayList<Node>(); for(Node sibling: allSiblings) { if (NodeUtil.isSame(node, sibling)) { break; } if (isNodeType(sibling, nodeTypeName)) { sameTypeSiblings.add(sibling); } } return sameTypeSiblings; } /** * Gets the siblings after this node with certain type. * @param node node from which will be siblings retrieved * @param nodeTypeName requested type of siblings nodes * @return list of siblings after the given Node (the given node is excluded) */ public static Iterable<Node> getSiblingsAfter(Node node, String nodeTypeName) throws RepositoryException { Node parent = node.getParent(); List<Node> allSiblings = NodeUtil.asList(NodeUtil.getNodes(parent)); int fromIndex = 0; for(Node sibling: allSiblings) { fromIndex++; if (NodeUtil.isSame(node, sibling)) { break; } } List<Node> sameTypeSiblings = new ArrayList<Node>(); for(Node sibling: allSiblings.subList(fromIndex, allSiblings.size())) { if (isNodeType(sibling, nodeTypeName)) { sameTypeSiblings.add(sibling); } } return sameTypeSiblings; } public static void moveNode(Node nodeToMove, Node newParent) throws RepositoryException { if (!isSame(newParent, nodeToMove.getParent())) { String newPath = combinePathAndName(newParent.getPath(), nodeToMove.getName()); nodeToMove.getSession().move(nodeToMove.getPath(), newPath); } } public static void moveNodeBefore(Node nodeToMove, Node target) throws RepositoryException { Node targetParent = target.getParent(); moveNode(nodeToMove, targetParent); targetParent.orderBefore(nodeToMove.getName(), target.getName()); } public static void moveNodeAfter(Node nodeToMove, Node target) throws RepositoryException { Node targetParent = target.getParent(); moveNode(nodeToMove, targetParent); orderAfter(nodeToMove, target.getName()); } public static boolean isFirstSibling(Node node) throws RepositoryException { Node parent = node.getParent(); NodeIterator nodes = parent.getNodes(); return isSame(nodes.nextNode(), node); } /** * Check if node1 and node2 are siblings. */ public static boolean isSameNameSiblings(Node node1, Node node2) throws RepositoryException { Node parent1 = node1.getParent(); Node parent2 = node2.getParent(); return isSame(parent1, parent2) && node1.getName().equals(node2.getName()); } public static boolean isLastSibling(Node node) throws RepositoryException { Node parent = node.getParent(); NodeIterator nodes = parent.getNodes(); Node last = null; while (nodes.hasNext()) { last = nodes.nextNode(); } return isSame(last, node); } public static void renameNode(Node node, String newName) throws RepositoryException { Node parent = node.getParent(); String newPath = combinePathAndName(parent.getPath(), newName); node.getSession().move(node.getPath(), newPath); } /** * @return Whether the provided node as the provided permission or not. * @throws RuntimeRepositoryException in case of RepositoryException. */ public static boolean isGranted(Node node, long permissions) { try { return PermissionUtil.isGranted(node, permissions); } catch (RepositoryException e) { throw new RuntimeRepositoryException(e); } } /** * Returns true if both arguments represents the same node. In case the nodes are wrapped the comparison is done one * the actual nodes behind the wrappers. */ public static boolean isSame(Node lhs, Node rhs) throws RepositoryException { return unwrap(lhs).isSame(unwrap(rhs)); } /** * @return a valid jcr path combined from the provided path and name. */ public static String combinePathAndName(String path, String name) { if ("/".equals(path)) { return "/" + name; } return path + "/" + name; } /** * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the * method will simply return it. */ public static Node createPath(Node parent, String relPath, String primaryNodeTypeName) throws RepositoryException, PathNotFoundException, AccessDeniedException { return createPath(parent, relPath, primaryNodeTypeName, false); } /** * Creates a node under the specified parent and relative path, then returns it. Should the node already exist, the * method will simply return it. */ public static Node createPath(Node parent, String relPath, String primaryNodeTypeName, boolean save) throws RepositoryException, PathNotFoundException, AccessDeniedException { // remove leading / String currentPath = StringUtils.removeStart(relPath, "/"); if (StringUtils.isEmpty(currentPath)) { return parent; } Node root = parent; String[] names = currentPath.split("/"); for (int i = 0; i < names.length; i++) { String name = names[i]; if (root.hasNode(name)) { root = root.getNode(name); } else { final Node newNode = root.addNode(name, primaryNodeTypeName); if (save) { root.getSession().save(); } root = newNode; } } return root; } /** * Visits the given node and then all of nodes beneath it except for metadata nodes and nodes of jcr type. */ public static void visit(Node node, NodeVisitor visitor) throws RepositoryException { visit(node, visitor, EXCLUDE_META_DATA_FILTER); } public static void visit(Node node, NodeVisitor visitor, Predicate predicate) throws RepositoryException { // TODO should it really visit the start node even if it doesn't match the filter? visitor.visit(node); for (Node child : getNodes(node, predicate)) { visit(child, visitor, predicate); } if (visitor instanceof PostNodeVisitor) { ((PostNodeVisitor) visitor).postVisit(node); } } public static Iterable<Node> getNodes(Node parent, Predicate predicate) throws RepositoryException { return asIterable(new FilteringNodeIterator(parent.getNodes(), predicate)); } public static Iterable<Node> getNodes(Node parent) throws RepositoryException { return getNodes(parent, EXCLUDE_META_DATA_FILTER); } public static Iterable<Node> getNodes(Node parent, String nodeTypeName) throws RepositoryException { return getNodes(parent, new NodeTypePredicate(nodeTypeName, false)); } public static Iterable<Node> asIterable(NodeIterator iterator) { return new NodeIterableAdapter(iterator); } public static List<Node> asList(Iterable<Node> nodes) { List<Node> nodesList = new ArrayList<Node>(); for (Node node : nodes) { nodesList.add(node); } return nodesList; } /** * This method return the node's name on success, otherwise it handles the {@link RepositoryException} by throwing a * {@link RuntimeRepositoryException}. */ public static String getName(Node content) { try { return content.getName(); } catch (RepositoryException e) { throw new RuntimeRepositoryException(e); } } /** * Get all children (by recursion) using MAGNOLIA_FILTER (filter accepting all nodes of a type with namespace mgnl). */ public static Iterable<Node> collectAllChildren(Node node) throws RepositoryException { List<Node> nodes = new ArrayList<Node>(); return collectAllChildren(nodes, node, MAGNOLIA_FILTER); } /** * Get all children (by recursion) using a Predicate. */ public static Iterable<Node> collectAllChildren(Node node, Predicate predicate) throws RepositoryException { List<Node> nodes = new ArrayList<Node>(); return collectAllChildren(nodes, node, predicate); } /** * Get all children (by recursion) using a Predicate. * // TODO this method should really be private or renamed */ public static Iterable<Node> collectAllChildren(List<Node> nodes, Node parent, Predicate predicate) throws RepositoryException { // get filtered sub nodes first nodes.addAll(asList(getNodes(parent, predicate))); // get all children to find recursively Iterator<Node> allChildren = getNodes(parent, EXCLUDE_META_DATA_FILTER).iterator(); // recursion while (allChildren.hasNext()) { collectAllChildren(nodes, allChildren.next(), predicate); } return nodes; } /** * Get all Ancestors until level 1. */ public static Collection<Node> getAncestors(Node node) throws RepositoryException { List<Node> allAncestors = new ArrayList<Node>(); int level = node.getDepth(); while (level != 0) { try { allAncestors.add((Node) node.getAncestor(--level)); } catch (AccessDeniedException e) { log.debug("Node " + node.getIdentifier() + " didn't allow access to Ancestor's "); } } return allAncestors; } /** * Used for building exception messages where we want to avoid handling another exception inside a throws clause. */ public static String getNodeIdentifierIfPossible(Node content) { try { return content.getIdentifier(); } catch (RepositoryException e) { return "<not available>"; } } public static String getNodePathIfPossible(Node node) { try { return node.getPath(); } catch (RepositoryException e) { return "<not available>"; } } /** * Return the Path of the node. * * @return the path for the node or an empty String in case of exception */ public static String getPathIfPossible(Node node) { try { return node.getPath(); } catch (RepositoryException e) { log.error("Failed to get handle: " + e.getMessage(), e); return StringUtils.EMPTY; } } public static NodeIterator filterNodeType(NodeIterator iterator, String nodeType){ return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.NodeTypePredicate(nodeType)); } public static NodeIterator filterDuplicates(NodeIterator iterator){ return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.DuplicateNodePredicate()); } public static NodeIterator filterParentNodeType(NodeIterator iterator, final String nodeType) throws RepositoryException{ return new FilteringNodeIterator(iterator, new info.magnolia.jcr.predicate.NodeTypeParentPredicate(nodeType)) { @Override public Node nextNode(){ Node node = super.nextNode(); try { while(node.getDepth() != 0 && !node.isNodeType(nodeType)){ if(node.getDepth() != 0){ node = node.getParent(); } } } catch (RepositoryException e) { throw new RuntimeException(e.getMessage(), e); } return node; } }; } public static Collection<Node> getCollectionFromNodeIterator(NodeIterator iterator){ Collection<Node> nodeCollection = new HashSet<Node>(150); while(iterator.hasNext()){ nodeCollection.add(iterator.nextNode()); } return nodeCollection; } //for n2b public static Collection<Node> getSortedCollectionFromNodeIterator(NodeIterator iterator){ Collection<Node> nodeCollection = new LinkedList<Node>(); while(iterator.hasNext()){ nodeCollection.add(iterator.nextNode()); } return nodeCollection; } /** * Write the properties of the node to the log. */ public static void traceNodeProperties(Node nodeOp) throws RepositoryException { // debug by logging properties. PropertyIterator propIter; propIter = nodeOp.getProperties(); log.info("Trace Node Properties:"); while (propIter.hasNext()) { Property prop = propIter.nextProperty(); if (prop.isMultiple()){ Value[] values = prop.getValues(); for (int i=0; i<values.length; i++){ log.info(prop.toString() + "[" + i + "] = " + upToNCharacters(values[i].getString(), 30)); } }else{ log.info(prop.toString() + " = " + upToNCharacters(prop.getString(), 30)); } } } /** * Write the children of the node to the log. */ public static void traceNodeChildren(Node nodeOp) throws RepositoryException { // debug by logging properties. NodeIterator nodeIter; nodeIter = nodeOp.getNodes(); log.info("Trace Node Children:"); while (nodeIter.hasNext()) { Node n = nodeIter.nextNode(); log.info(n.toString()); } } private static String upToNCharacters(String s, int n) { return s.substring(0, Math.min(s.length(), n)); } }