com.day.cq.commons.jcr.JcrUtil Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/* * Copyright 1997-2008 Day Management AG * Barfuesserplatz 6, 4001 Basel, Switzerland * All Rights Reserved. * * This software is the confidential and proprietary information of * Day Management AG, ("Confidential Information"). You shall not * disclose such Confidential Information and shall use it only in * accordance with the terms of the license agreement you entered into * with Day. */ package com.day.cq.commons.jcr; import com.day.text.Text; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Binary; import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.ValueFactory; import javax.jcr.nodetype.NodeType; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Writer; import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.ListIterator; import java.util.StringTokenizer; /** * Utility for common JCR tasks */ public class JcrUtil { /** * default logger */ private static final Logger log = LoggerFactory.getLogger(JcrUtil.class); /** * Escapes all illegal JCR name characters of a string. * The encoding is loosely modeled after URI encoding, but only encodes * the characters it absolutely needs to in order to make the resulting * string a valid JCR name. * Use {@link #unescapeIllegalJcrChars(String)} for decoding. *
. Generates a valid name and test if child * already exists. If name is already existing, iterate until a unique one is found * * @param node parent node * @param name the name to check * @return a valid label string * @throws RepositoryException in case of error, accessing the Repository */ public static String createValidChildName(Node node, String name) throws RepositoryException { String valid = createValidName(name); if (node.hasNode(valid)) { // leaf node already exists, create new unique name String leafNodeName; int i = 0; do { leafNodeName = valid + String.valueOf(i); i++; } while (node.hasNode(leafNodeName)); return leafNodeName; } return valid; } /** * Checks if the name is not empty and contains only valid chars. * @param name the name to check * @return* QName EBNF:
** simplename ::= onecharsimplename | twocharsimplename | threeormorecharname * onecharsimplename ::= (* Any Unicode character except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace character *) * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' | onecharsimplename onecharsimplename * threeormorecharname ::= nonspace string nonspace * string ::= char | string char * char ::= nonspace | ' ' * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace character *) ** * @param name the name to escape * @return the escaped name */ public static String escapeIllegalJcrChars(String name) { StringBuffer buffer = new StringBuffer(name.length() * 2); for (int i = 0; i < name.length(); i++) { char ch = name.charAt(i); if (ch == '%' || ch == '/' || ch == ':' || ch == '[' || ch == ']' || ch == '*' || ch == '\'' || ch == '"' || ch == '|' || (ch == '.' && name.length() < 3) || (ch == ' ' && (i == 0 || i == name.length() - 1)) || ch == '\t' || ch == '\r' || ch == '\n') { buffer.append('%'); buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16))); buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16))); } else { buffer.append(ch); } } return buffer.toString(); } /** * Unescapes previously escaped jcr chars. ** Please note, that this does not exactly the same as the url related * {@link com.day.text.Text#unescape(String)}, since it handles the byte-encoding * differently. * * @param name the name to unescape * @return the unescaped name */ public static String unescapeIllegalJcrChars(String name) { StringBuffer buffer = new StringBuffer(name.length()); int i = name.indexOf('%'); while (i > -1 && i + 2 < name.length()) { buffer.append(name.toCharArray(), 0, i); int a = Character.digit(name.charAt(i + 1), 16); int b = Character.digit(name.charAt(i + 2), 16); if (a > -1 && b > -1) { buffer.append((char) (a * 16 + b)); name = name.substring(i + 3); } else { buffer.append('%'); name = name.substring(i + 1); } i = name.indexOf('%'); } buffer.append(name); return buffer.toString(); } /** * Creates or gets the {@link javax.jcr.Node Node} at the given Path. * In case it has to create the Node all non-existent intermediate path-elements * will be created with the given NodeType. * *
* Changes made are not saved by this method, so
session.save()
* has to be called to persist them. * * @param absolutePath absolute path to create * @param nodeType to use for creation of nodes * @param session to use * @return the Node at path * @throws RepositoryException in case of exception accessing the Repository */ public static Node createPath(String absolutePath, String nodeType, Session session) throws RepositoryException { return createPath(absolutePath, false, nodeType, nodeType, session, false); } /** * Creates or gets the {@link javax.jcr.Node Node} at the given Path. * In case it has to create the Node all non-existent intermediate path-elements * will be created with the given intermediate node type and the returned node * will be created with the given nodeType. * * @param absolutePath absolute path to create * @param intermediateNodeType to use for creation of intermediate nodes * @param nodeType to use for creation of the final node * @param session to use * @param autoSave Should save be called when a new node is created? * @return the Node at absolutePath * @throws RepositoryException in case of exception accessing the Repository */ public static Node createPath(String absolutePath, String intermediateNodeType, String nodeType, Session session, boolean autoSave) throws RepositoryException { return createPath(absolutePath, false, intermediateNodeType, nodeType, session, autoSave); } /** * Creates a {@link javax.jcr.Node Node} at the given Path. In case it has * to create the Node all non-existent intermediate path-elements will be * created with the given intermediate node type and the returned node will * be created with the given nodeType. * ** If the path points to an existing node, the leaf node name will be * regarded as a name hint and a unique node name will be created by * appending a number to the given name (eg.
/some/path/foobar2
). * Please note that the uniqueness check is not an atomic JCR operation, * so it is possible that you get a {@link RepositoryException} (path * already exists) if another concurrent session created the same node in * the meantime. * ** Changes made are not saved by this method, so
session.save()
* has to be called to persist them. * * @param pathHint * path to create * @param nodeType * to use for creation of nodes * @param session * to use * @return the newly created Node * @throws RepositoryException * in case of exception accessing the Repository */ public static Node createUniquePath(String pathHint, String nodeType, Session session) throws RepositoryException { return createPath(pathHint, true, nodeType, nodeType, session, false); } /** * Creates or gets the {@link javax.jcr.Node Node} at the given Path. In * case it has to create the Node all non-existent intermediate * path-elements will be created with the given intermediate node type and * the returned node will be created with the given nodeType. * ** If the parameter
createUniqueLeaf
is set, it will not get * an existing node but rather try to create a unique node by appending a * number to the last path element (leaf node). Please note that the * uniqueness check is not an atomic JCR operation, so it is possible * that you get a {@link RepositoryException} (path already exists) if * another concurrent session created the same node in the meantime. * * @param absolutePath * absolute path to create * @param createUniqueLeaf * whether the leaf of the path should be regarded as a name hint * and a unique node name should be created by appending a number * to the given name (eg./some/path/foobar2
) * @param intermediateNodeType * to use for creation of intermediate nodes * @param nodeType * to use for creation of the final node * @param session * to use * @param autoSave * Should save be called when a new node is created? * @return the Node at absolutePath * @throws RepositoryException * in case of exception accessing the Repository */ public static Node createPath(String absolutePath, boolean createUniqueLeaf, String intermediateNodeType, String nodeType, Session session, boolean autoSave) throws RepositoryException { if (absolutePath == null || absolutePath.length() == 0 || "/".equals(absolutePath)) { // path denotes root node return session.getRootNode(); } else { // See SLING-3361 String existingPath = findExistingPath(absolutePath, session); String relativePath = null; Node baseNode = null; if (existingPath != null) { baseNode = session.getNode(existingPath); relativePath = absolutePath.substring(existingPath.length() + 1); } else { relativePath = absolutePath.substring(1); baseNode = session.getRootNode(); } return createPath(baseNode, relativePath, createUniqueLeaf, intermediateNodeType, nodeType, session, autoSave); } } /** * Creates or gets the {@link javax.jcr.Node Node} at the given Path. In * case it has to create the Node all non-existent intermediate * path-elements will be created with the given intermediate node type and * the returned node will be created with the given nodeType. * ** If the node name points to an existing node, the node name will be * regarded as a name hint and a unique node name will be created by * appending a number to the given name (eg.
/some/path/foobar2
). * Please note that the uniqueness check is not an atomic JCR operation, * so it is possible that you get a {@link RepositoryException} (path * already exists) if another concurrent session created the same node in * the meantime. * ** Changes made are not saved by this method, so
session.save()
* has to be called to persist them. * * @param parent * existing parent node for the new node * @param nodeNameHint * name hint for the new node * @param nodeType * to use for creation of the node * @param session * to use * @return the newly created Node * @throws RepositoryException * in case of exception accessing the Repository */ public static Node createUniqueNode(Node parent, String nodeNameHint, String nodeType, Session session) throws RepositoryException { return createPath(parent, nodeNameHint, true, nodeType, nodeType, session, false); } /** * Creates or gets the {@link javax.jcr.Node Node} at the given path * relative to the baseNode. In case it has to create the Node all * non-existent intermediate path-elements will be created with the given * intermediate node type and the returned node will be created with the * given nodeType. * ** If the parameter
createUniqueLeaf
is set, it will not get * an existing node but rather try to create a unique node by appending a * number to the last path element (leaf node). Please note that the * uniqueness check is not an atomic JCR operation, so it is possible * that you get a {@link RepositoryException} (path already exists) if * another concurrent session created the same node in the meantime. * * @param baseNode * existing node that should be the base for the relative path * @param path * relative path to create * @param createUniqueLeaf * whether the leaf of the path should be regarded as a name hint * and a unique node name should be created by appending a number * to the given name (eg./some/path/foobar2
) * @param intermediateNodeType * to use for creation of intermediate nodes * @param nodeType * to use for creation of the final node * @param session * to use * @param autoSave * Should save be called when a new node is created? * @return the Node at path * @throws RepositoryException * in case of exception accessing the Repository */ public static Node createPath(Node baseNode, String path, boolean createUniqueLeaf, String intermediateNodeType, String nodeType, Session session, boolean autoSave) throws RepositoryException { if (!createUniqueLeaf && baseNode.hasNode(path)) { // node at path already exists, quicker way return baseNode.getNode(path); } Node node = baseNode; // walk up tree and find first existing node (see CQ5-12197) String fullPath = node.getPath().endsWith("/") ? node.getPath() + path : node.getPath() + "/" + path; String parentPath = Text.getRelativeParent(fullPath, 1); while(parentPath.length() > 0 && !session.nodeExists(parentPath)) { parentPath = Text.getRelativeParent(parentPath, 1); } path = fullPath.substring(parentPath.length()); node = session.getNode(parentPath + "/"); // intermediate path elements int pos = path.lastIndexOf('/'); if (pos != -1) { final StringTokenizer st = new StringTokenizer(path.substring(0, pos), "/"); while (st.hasMoreTokens()) { final String token = st.nextToken(); if (!node.hasNode(token)) { try { node.addNode(token, intermediateNodeType); if (autoSave) session.save(); } catch (RepositoryException e) { log.warn("Error while creating intermediate node", e); // we ignore this as this folder might be created from a different task node.refresh(false); } } node = node.getNode(token); } path = path.substring(pos + 1); } // last path element (path = leaf node name) if (!node.hasNode(path)) { node.addNode(path, nodeType); if (autoSave) session.save(); } else if (createUniqueLeaf) { // leaf node already exists, create new unique name String leafNodeName; int i = 0; do { leafNodeName = path + String.valueOf(i); i++; } while (node.hasNode(leafNodeName)); Node leaf = node.addNode(leafNodeName, nodeType); if (autoSave) session.save(); return leaf; } return node.getNode(path); } /** * the list of replacement string for non-valid jcr characters. illegal * characters are replaced by an underscore ("_"). */ public final static String[] STANDARD_LABEL_CHAR_MAPPING = new String[] { "_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_", "_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_", "_","_","_","_","_","_","_","_","_","_","_","_","_","-","_","_", "0","1","2","3","4","5","6","7","8","9","_","_","_","_","_","_", "_","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", "p","q","r","s","t","u","v","w","x","y","z","_","_","_","_","_", "_","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o", "p","q","r","s","t","u","v","w","x","y","z","_","_","_","_","_", "_","f","_","_","_","fi","fi","_","_","_","_","_","_","_","_","_", "_","_","_","_","_","_","_","_","_","_","_","_","y","_","_","_", "_","i","c","p","o","v","_","s","_","_","_","_","_","_","_","_", "_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_", "a","a","a","a","ae","a","ae","c","e","e","e","e","i","i","i","i", "d","n","o","o","o","o","oe","x","o","u","u","u","ue","y","b","ss", "a","a","a","a","ae","a","ae","c","e","e","e","e","i","i","i","i", "o","n","o","o","o","o","oe","_","o","u","u","u","ue","y","b","y" }; /** * the list of replacement string for non-valid jcr characters. illegal * characters are replaced by a hyphen ("-"). */ public final static String[] HYPHEN_LABEL_CHAR_MAPPING = new String[STANDARD_LABEL_CHAR_MAPPING.length]; static { for (int i=0; i=0 && c node true
if the name is valid */ public static boolean isValidName(String name) { if (name==null || name.equals("")) { return false; } for (int idx=0; idx=0 || name.charAt(idx) < 32 || name.charAt(idx) >= 127) { return false; } } return true; } /** * Creates a {@link javax.jcr.Value JCR Value} for the given object with * the given Session. * Selects the the {@link javax.jcr.PropertyType PropertyType} according * the instance of the object's Class * * @param value object * @param session to create value for * @return the value or null if not convertible to a valid PropertyType * @throws RepositoryException in case of error, accessing the Repository */ public static Value createValue(Object value, Session session) throws RepositoryException { Value val; ValueFactory fac = session.getValueFactory(); if (value instanceof Calendar) { val = fac.createValue((Calendar) value); } else if (value instanceof InputStream) { val = fac.createValue(session.getValueFactory().createBinary((InputStream) value)); } else if (value instanceof Node) { val = fac.createValue((Node) value); } else if (value instanceof Long) { val = fac.createValue((Long) value); } else if (value instanceof Number) { val = fac.createValue(((Number) value).doubleValue()); } else if (value instanceof Boolean) { val = fac.createValue((Boolean) value); } else if (value instanceof String) { val = fac.createValue((String) value); } else { val = null; } return val; } /** * Sets the value of the property. * Selects the {@link javax.jcr.PropertyType PropertyType} according * to the instance of the object's class. * * @param node The node where the property will be set on. * @param propertyName The name of the property. * @param propertyValue The value for the property. * @throws RepositoryException if a repository error occurs */ public static void setProperty(final Node node, final String propertyName, final Object propertyValue) throws RepositoryException { if (propertyValue == null) { node.setProperty(propertyName, (String) null); } else if (propertyValue.getClass().isArray()) { final Object[] values = (Object[]) propertyValue; final Value[] setValues = new Value[values.length]; for (int i = 0; i < values.length; i++) { setValues[i] = createValue(values[i], node.getSession()); } node.setProperty(propertyName, setValues); } else { node.setProperty(propertyName, createValue(propertyValue, node.getSession())); } } /** * Copy the src
item into thedstParent
node. * The name of the newly created item is set toname
. * * @param src The item to copy to the new location * @param dstParent The node into which thesrc
node is to be * copied * @param name The name of the newly created item. If this is *null
the new item gets the same name as the *src
item. * @return the new item * @throws RepositoryException May be thrown in case of any problem copying * the content. * @see #copy(Node, Node, String) * @see #copy(Property , Node, String) */ public static Item copy(Item src, Node dstParent, String name) throws RepositoryException { if (src.isNode()) { return copy((Node) src, dstParent, name); } else { return copy((Property) src, dstParent, name); } } /** * Copy thesrc
node into thedstParent
node. * The name of the newly created node is set toname
. ** This method does a recursive (deep) copy of the subtree rooted at the * source node to the destination. Any protected child nodes and and * properties are not copied. * * @param src The node to copy to the new location * @param dstParent The node into which the
src
node is to be * copied * @param name The name of the newly created node. If this is *null
the new node gets the same name as the *src
node. * @return the newly created node * @throws RepositoryException May be thrown in case of any problem copying * the content. */ public static Node copy(Node src, Node dstParent, String name) throws RepositoryException { return copy(src, dstParent, name, false); } /** * Copy thesrc
node into thedstParent
node. * The name of the newly created node is set toname
. ** This method does a recursive (deep) copy of the subtree rooted at the * source node to the destination. Any protected child nodes and and * properties are not copied. The
bestEffort
argument specifies * whether or not copying is aborted when adding a mixin, creating a property * or creating a child node fails with a {@link RepositoryException}. * * @param src The node to copy to the new location * @param dstParent The node into which thesrc
node is to be * copied * @param name The name of the newly created node. If this is *null
the new node gets the same name as the *src
node. * @param bestEfforttrue
for best effort copying: skip mixins, * properties and child nodes which cannot be added. * @return the newly created node * @throws RepositoryException May be thrown in case of any problem copying * the content. */ public static Node copy(Node src, Node dstParent, String name, boolean bestEffort) throws RepositoryException { // ensure destination name if (name == null) { name = src.getName(); } // ensure new node creation if (dstParent.hasNode(name)) { dstParent.getNode(name).remove(); } // create new node Node dst = dstParent.addNode(name, src.getPrimaryNodeType().getName()); for (NodeType mix : src.getMixinNodeTypes()) { try { dst.addMixin(mix.getName()); } catch (RepositoryException e) { if (!bestEffort) { throw e; } } } // copy the properties for (PropertyIterator iter = src.getProperties(); iter.hasNext();) { try { copy(iter.nextProperty(), dst, null); } catch (RepositoryException e) { if (!bestEffort) { throw e; } } } // copy the child nodes for (NodeIterator iter = src.getNodes(); iter.hasNext();) { Node n = iter.nextNode(); if (!n.getDefinition().isProtected()) { try { copy(n, dst, null, bestEffort); } catch (RepositoryException e) { if (!bestEffort) { throw e; } } } } return dst; } /** * Copy thesrc
property into thedstParent
* node. The name of the newly created property is set toname
. ** If the source property is protected, this method does nothing. * * @param src The property to copy to the new location * @param dstParent The node into which the
src
property is * to be copied * @param name The name of the newly created property. If this is *null
the new property gets the same name as the *src
property. * @return the new property ornull
if nothing was copied * @throws RepositoryException May be thrown in case of any problem copying * the content. */ public static Property copy(Property src, Node dstParent, String name) throws RepositoryException { if (!src.getDefinition().isProtected()) { if (name == null) { name = src.getName(); } // ensure new property creation if (dstParent.hasProperty(name)) { dstParent.getProperty(name).remove(); } if (src.getDefinition().isMultiple()) { return dstParent.setProperty(name, src.getValues()); } else { return dstParent.setProperty(name, src.getValue()); } } return null; } /** * Restores the odering of the nodes according to the given comma seperated * name list. Please note, that no changes are saved. * * @param parent the parent node * @param nameList the list of names * @returntrue
if the nodes were reordered * @throws RepositoryException if an error occurrs. */ public static boolean setChildNodeOrder(Node parent, String nameList) throws RepositoryException { return nameList != null && setChildNodeOrder(parent, Text.explode(nameList, ',')); } /** * Restores the odering of the nodes according to the given * name list. Please note, that no changes are saved. * * @param parent the parent node * @param names the list of names * @returntrue
if the nodes were reordered * @throws RepositoryException if an error occurrs. */ public static boolean setChildNodeOrder(Node parent, String[] names) throws RepositoryException { if (names.length > 1 && parent.getPrimaryNodeType().hasOrderableChildNodes()) { // quick check if node is checked out if (!parent.isCheckedOut()) { log.warn("Unable to restore order of a checked-in node: " + parent.getPath()); return false; } String last = null; Listlist = Arrays.asList(names); ListIterator iter = list.listIterator(list.size()); while (iter.hasPrevious()) { String prev = iter.previous(); if (parent.hasNode(prev)) { log.debug("ordering {} before {}", prev, last); parent.orderBefore(prev, last); last = prev; } } return true; } return false; } /** * Copies the contents of the resource to the given writer. The resource * at path
needs to have a binary property at the relative * path "jcr:content/jcr:data". the string is encoded using the optional * "jcr:content/jcr:encoding" property. * * @param session session * @param path file node path * @param out the writer * @throws IOException if an I/O error occurs * @throws RepositoryException if a repository error occurs * @return the number of writter characters */ public static int copyResource(Session session, String path, Writer out) throws IOException, RepositoryException{ InputStream is = null; Binary b = null; try { Node content = session.getNode(path + "/" + JcrConstants.JCR_CONTENT); b = content.getProperty(JcrConstants.JCR_DATA).getBinary(); is = b.getStream(); String encoding = content.hasProperty(JcrConstants.JCR_ENCODING) ? content.getProperty(JcrConstants.JCR_ENCODING).getString() : "utf-8"; InputStreamReader r = new InputStreamReader(is, encoding); return IOUtils.copy(r, out); } finally { IOUtils.closeQuietly(is); if (b != null) { b.dispose(); } } } private static String findExistingPath(String path, Session session) throws RepositoryException { //find the parent that exists // we can start from the youngest child in tree int currentIndex = path.lastIndexOf('/'); String temp = path; String existingPath = null; while (currentIndex > 0) { temp = temp.substring(0, currentIndex); //break when first existing parent is found if (session.itemExists(temp)) { existingPath = temp; break; } currentIndex = temp.lastIndexOf("/"); } return existingPath; } }