/** * This file Copyright (c) 2003-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.cms.util; import info.magnolia.cms.core.Content; import info.magnolia.cms.core.Content.ContentFilter; import info.magnolia.cms.core.DefaultContent; import info.magnolia.cms.core.HierarchyManager; import info.magnolia.cms.core.ItemType; import info.magnolia.cms.core.Path; import info.magnolia.cms.security.AccessDeniedException; import info.magnolia.context.MgnlContext; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Some easy to use methods to handle with Content objects. * * @version $Id$ * * @deprecated since 4.5 - use {@link info.magnolia.jcr.util.NodeUtil} instead. */ @Deprecated public class ContentUtil { private final static Logger log = LoggerFactory.getLogger(ContentUtil.class); /** * Content filter accepting everything. */ public static ContentFilter ALL_NODES_CONTENT_FILTER = new ContentFilter() { @Override public boolean accept(Content content) { return true; } }; /** * Content filter accepting everything exept nodes with namespace jcr (version and system store). */ public static ContentFilter ALL_NODES_EXCEPT_JCR_CONTENT_FILTER = new ContentFilter() { @Override public boolean accept(Content content) { return !content.getName().startsWith("jcr:"); } }; /** * Content filter accepting everything except meta data and jcr types. */ public static ContentFilter EXCLUDE_META_DATA_CONTENT_FILTER = new ContentFilter() { @Override public boolean accept(Content content) { return !content.getName().startsWith("jcr:") && !content.isNodeType(ItemType.NT_METADATA); } }; /** * Content filter accepting all nodes with a nodetype of namespace mgnl. */ public static ContentFilter MAGNOLIA_FILTER = new ContentFilter() { @Override public boolean accept(Content content) { try { String nodetype = content.getNodeType().getName(); // export only "magnolia" nodes return nodetype.startsWith("mgnl:"); } catch (RepositoryException e) { log.error("Unable to read nodetype for node {}", content.getHandle()); } return false; } }; /** * Used in {@link #visit(Content)} to visit the hierarchy. * @version $Id$ */ // TODO : throws RepositoryException or none, but not Exception !? public static interface Visitor { void visit(Content node) throws Exception; } /** * Used in {@link #visit(Content)} if the visitor wants to use post order. */ public static interface PostVisitor extends Visitor { void postVisit(Content node) throws Exception; } /** * Returns a Content object of the named repository or null if not existing. * @return null if not found */ public static Content getContent(String repository, String path) { try { return MgnlContext.getHierarchyManager(repository).getContent(path); } catch (RepositoryException e) { return null; } } /** * @return null if not found */ public static Content getContentByUUID(String repository, String uuid) { try { return MgnlContext.getHierarchyManager(repository).getContentByUUID(uuid); } catch (RepositoryException e) { return null; } } /** * Get the node or null if not exists. * @param node * @param name * @return the sub node */ public static Content getContent(Content node, String name) { try { return node.getContent(name); } catch (RepositoryException e) { return null; } } /** * If the node doesn't exist just create it. Attention the method does not save the newly created node. */ public static Content getOrCreateContent(Content node, String name, ItemType contentType) throws AccessDeniedException, RepositoryException{ return getOrCreateContent(node, name, contentType, false); } /** * If the node doesn't exist just create it. If the parameter save is true the parent node is saved. */ public static Content getOrCreateContent(Content node, String name, ItemType contentType, boolean save) throws AccessDeniedException, RepositoryException { Content res = null; try { res = node.getContent(name); } catch (PathNotFoundException e) { res = node.createContent(name, contentType); if(save){ res.getParent().save(); } } return res; } /** * Get a subnode case insensitive. * @param node * @param name */ public static Content getCaseInsensitive(Content node, String name) { if (name == null || node == null) { return null; } name = name.toLowerCase(); for (Content child : node.getChildren(ALL_NODES_CONTENT_FILTER)) { if (child.getName().toLowerCase().equals(name)) { return child; } } return null; } /** * Get all children recursively (content and contentnode). */ public static List<Content> collectAllChildren(Content node) { List<Content> nodes = new ArrayList<Content>(); return collectAllChildren(nodes, node, new ItemType[]{ItemType.CONTENT, ItemType.CONTENTNODE}); } /** * Get all children using a filter. * @param node * @param filter * @return list of all found nodes */ public static List<Content> collectAllChildren(Content node, ContentFilter filter) { List<Content> nodes = new ArrayList<Content>(); return collectAllChildren(nodes, node, filter); } /** * Get the children using a filter. * @param nodes collection of already found nodes */ private static List<Content> collectAllChildren(List<Content> nodes, Content node, ContentFilter filter) { // get filtered sub nodes first Collection<Content> children = node.getChildren(filter); for (Content child : children) { nodes.add(child); } // get all children to find recursively Collection<Content> allChildren = node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER); // recursion for (Content child : allChildren) { collectAllChildren(nodes, child, filter); } return nodes; } /** * Get all children of a particular type. */ public static List<Content> collectAllChildren(Content node, ItemType type) { List<Content> nodes = new ArrayList<Content>(); return collectAllChildren(nodes, node, new ItemType[]{type}); } /** * Returns all children (not recursively) independent of there type. */ public static Collection<Content> getAllChildren(Content node){ return node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER); } /** * Returns all children (not recursively) independent of there type. */ public static Collection<Content> getAllChildren(Content node, Comparator<Content> comp){ return node.getChildren(EXCLUDE_META_DATA_CONTENT_FILTER, comp); } /** * Get all children of a particular type. */ public static List<Content> collectAllChildren(Content node, ItemType[] types) { List<Content> nodes = new ArrayList<Content>(); return collectAllChildren(nodes, node, types); } /** * Get all subnodes recursively and add them to the nodes collection. */ private static List<Content> collectAllChildren(List<Content> nodes, Content node, ItemType[] types) { for (int i = 0; i < types.length; i++) { ItemType type = types[i]; Collection<Content> children = node.getChildren(type); for (Content child : children) { nodes.add(child); collectAllChildren(nodes, child, types); } } return nodes; } /** * Returns the first found <strong>ancestor</strong> of the given node which is of the given type, * or the given node itself, it is of the given type. */ public static Content getAncestorOfType(final Content firstNode, final String nodeType) throws RepositoryException { Content node = firstNode; while (!node.isNodeType(nodeType)) { node = node.getParent(); if (node.getLevel() == 0) { throw new RepositoryException("No ancestor of type " + nodeType + " found for " + firstNode); } } return node; } /** * Convenient method to order a node before a target node. */ public static void orderBefore(Content nodeToMove, String targetNodeName) throws RepositoryException{ nodeToMove.getParent().orderBefore(nodeToMove.getName(), targetNodeName); } /** * Convenient method for ordering a node after a specific target node. This is not that simple as jcr only supports ordering before a node. */ public static void orderAfter(Content nodeToMove, String targetNodeName) throws RepositoryException{ Content parent = nodeToMove.getParent(); Boolean readyToMove = false; Collection<Content> children = new ArrayList<Content>(ContentUtil.getAllChildren(parent)); for (Content child : children) { if(readyToMove){ parent.orderBefore(nodeToMove.getName(), child.getName()); readyToMove = false; break; } if(child.getName().equals(targetNodeName)){ readyToMove = true; } } if(readyToMove){ for (Content child : children){ if(!nodeToMove.getName().equals(child.getName())){ parent.orderBefore(child.getName(), nodeToMove.getName()); } } } } public static void orderNodes(Content node, String[] nodes) throws RepositoryException{ for (int i = nodes.length - 1; i > 0; i--) { node.orderBefore(nodes[i-1], nodes[i]); } } /** * Uses the passed comparator to create the jcr ordering of the children. */ public static void orderNodes(Content node, Comparator<Content> comparator) throws RepositoryException { Collection<Content> children = ContentUtil.getAllChildren(node, comparator); String[] names = new String[children.size()]; int i = 0; for (Content childNode : children) { names[i] = childNode.getName(); i++; } orderNodes(node, names); } public static void visit(Content node, Visitor visitor) throws Exception{ visit(node, visitor, EXCLUDE_META_DATA_CONTENT_FILTER); } public static void visit(Content node, Visitor visitor, ContentFilter filter) throws Exception{ visitor.visit(node); for (Content content : node.getChildren(filter)) { visit(content, visitor, filter); } if(visitor instanceof PostVisitor){ ((PostVisitor)visitor).postVisit(node); } } public static Content createPath(HierarchyManager hm, String path) throws AccessDeniedException, PathNotFoundException, RepositoryException { return createPath(hm, path, false); } public static Content createPath(HierarchyManager hm, String path, boolean save) throws AccessDeniedException, PathNotFoundException, RepositoryException { return createPath(hm, path, ItemType.CONTENT, save); } public static Content createPath(HierarchyManager hm, String path, ItemType type) throws AccessDeniedException, PathNotFoundException, RepositoryException { return createPath(hm, path, type, false); } public static Content createPath(HierarchyManager hm, String path, ItemType type, boolean save) throws AccessDeniedException, PathNotFoundException, RepositoryException { Content root = hm.getRoot(); return createPath(root, path, type, save); } public static Content createPath(Content parent, String path, ItemType type) throws RepositoryException, PathNotFoundException, AccessDeniedException { return createPath(parent, path, type, false); } public static Content createPath(Content parent, String path, ItemType type, boolean save) throws RepositoryException, PathNotFoundException, AccessDeniedException { // remove leading / path = StringUtils.removeStart(path, "/"); if (StringUtils.isEmpty(path)) { return parent; } String[] names = path.split("/"); //$NON-NLS-1$ for (int i = 0; i < names.length; i++) { String name = names[i]; if (parent.hasContent(name)) { parent = parent.getContent(name); } else { final Content newNode = parent.createContent(name, type); if(save){ parent.save(); } parent = newNode; } } return parent; } public static String uuid2path(String repository, String uuid){ if(StringUtils.isNotEmpty(uuid)){ HierarchyManager hm = MgnlContext.getHierarchyManager(repository); try { Content node = hm.getContentByUUID(uuid); return node.getHandle(); } catch (Exception e) { // return the uuid } } return uuid; } public static String path2uuid(String repository, String path) { if(StringUtils.isNotEmpty(path)){ HierarchyManager hm = MgnlContext.getHierarchyManager(repository); try { Content node = hm.getContent(path); return node.getUUID(); } catch (Exception e) { // return the uuid } } return path; } public static void deleteAndRemoveEmptyParents(Content node) throws PathNotFoundException, RepositoryException, AccessDeniedException { deleteAndRemoveEmptyParents(node, 0); } public static void deleteAndRemoveEmptyParents(Content node, int level) throws PathNotFoundException, RepositoryException, AccessDeniedException { Content parent = null; if(node.getLevel() != 0){ parent = node.getParent(); } node.delete(); if(parent != null && parent.getLevel()>level && parent.getChildren(ContentUtil.EXCLUDE_META_DATA_CONTENT_FILTER).size()==0){ deleteAndRemoveEmptyParents(parent, level); } } /** * Session based copy operation. As JCR only supports workspace based copies this operation is performed * by using export import operations. */ public static void copyInSession(Content src, String dest) throws RepositoryException { final String destParentPath = StringUtils.defaultIfEmpty(StringUtils.substringBeforeLast(dest, "/"), "/"); final String destNodeName = StringUtils.substringAfterLast(dest, "/"); final Session session = src.getWorkspace().getSession(); try{ final File file = File.createTempFile("mgnl", null, Path.getTempDirectory()); final FileOutputStream outStream = new FileOutputStream(file); session.exportSystemView(src.getHandle(), outStream, false, false); outStream.flush(); IOUtils.closeQuietly(outStream); FileInputStream inStream = new FileInputStream(file); session.importXML( destParentPath, inStream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); IOUtils.closeQuietly(inStream); file.delete(); if(!StringUtils.equals(src.getName(), destNodeName)){ String currentPath; if(destParentPath.equals("/")){ currentPath = "/" + src.getName(); } else{ currentPath = destParentPath + "/" + src.getName(); } session.move(currentPath, dest); } } catch (IOException e) { throw new RepositoryException("Can't copy node " + src + " to " + dest, e); } } /** * Magnolia uses by default workspace move operation to move nodes. This is a util method to move a node inside a session. */ public static void moveInSession(Content src, String dest) throws RepositoryException { src.getWorkspace().getSession().move(src.getHandle(), dest); } public static void rename(Content node, String newName) throws RepositoryException{ Content parent = node.getParent(); String placedBefore = null; for (Iterator<Content> iter = parent.getChildren(node.getNodeTypeName()).iterator(); iter.hasNext();) { Content child = iter.next(); if (child.getUUID().equals(node.getUUID())) { if (iter.hasNext()) { child = iter.next(); placedBefore = child.getName(); } } } moveInSession(node, PathUtil.createPath(node.getParent().getHandle(), newName)); // now set at the same place as before if (placedBefore != null) { parent.orderBefore(newName, placedBefore); parent.save(); } } /** * Utility method to change the <code>jcr:primaryType</code> value of a node. * @param node - {@link Content} the node whose type has to be changed * @param newType - {@link ItemType} the new node type to be assigned * @param replaceAll - boolean when <code>true</code> replaces all occurrences * of the old node type. When <code>false</code> replaces only the first occurrence. * @throws RepositoryException */ public static void changeNodeType(Content node, ItemType newType, boolean replaceAll) throws RepositoryException{ if(node == null){ throw new IllegalArgumentException("Content can't be null"); } if(newType == null){ throw new IllegalArgumentException("ItemType can't be null"); } final String oldTypeName = node.getNodeTypeName(); final String newTypeName = newType.getSystemName(); if(newTypeName.equals(oldTypeName)){ log.info("Old node type and new one are the same {}. Nothing to change.", newTypeName); return; } final Pattern nodeTypePattern = Pattern.compile("(<sv:property\\s+sv:name=\"jcr:primaryType\"\\s+sv:type=\"Name\"><sv:value>)("+oldTypeName+")(</sv:value>)"); final String replacement = "$1"+newTypeName+"$3"; log.debug("pattern is {}", nodeTypePattern.pattern()); log.debug("replacement string is {}", replacement); log.debug("replaceAll? {}", replaceAll); final String destParentPath = StringUtils.defaultIfEmpty(StringUtils.substringBeforeLast(node.getHandle(), "/"), "/"); final Session session = node.getWorkspace().getSession(); FileOutputStream outStream = null; FileInputStream inStream = null; File file = null; try { file = File.createTempFile("mgnl", null, Path.getTempDirectory()); outStream = new FileOutputStream(file); session.exportSystemView(node.getHandle(), outStream, false, false); outStream.flush(); final String fileContents = FileUtils.readFileToString(file); log.debug("content string is {}", fileContents); final Matcher matcher = nodeTypePattern.matcher(fileContents); String replaced = null; log.debug("starting find&replace..."); long start = System.currentTimeMillis(); if(matcher.find()) { log.debug("{} will be replaced", node.getHandle()); if(replaceAll){ replaced = matcher.replaceAll(replacement); } else { replaced = matcher.replaceFirst(replacement); } log.debug("replaced string is {}", replaced); } else { log.debug("{} won't be replaced", node.getHandle()); return; } log.debug("find&replace operations took {}ms", (System.currentTimeMillis() - start)); FileUtils.writeStringToFile(file, replaced); inStream = new FileInputStream(file); session.importXML( destParentPath, inStream, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); } catch (IOException e) { throw new RepositoryException("Can't replace node " + node.getHandle(), e); } finally { IOUtils.closeQuietly(outStream); IOUtils.closeQuietly(inStream); FileUtils.deleteQuietly(file); } } public static Content asContent(Node content) { if(content == null) { return null; } return new DefaultContent(content); } }