X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcr.java;h=72e325d35a40c40ad22a712ae7561fd33ae6ed87;hb=98ff62f2df01366777fda9b4dbe7586b0cd45252;hp=263128448f3f5f133b194c91cf2f647013d5b5d2;hpb=a2590cf3e2ad039f004f13ef6c97a9f702841e5b;p=lgpl%2Fargeo-commons.git diff --git a/org.argeo.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java index 263128448..72e325d35 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/Jcr.java +++ b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java @@ -1,8 +1,13 @@ package org.argeo.jcr; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.text.MessageFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; @@ -22,12 +27,16 @@ import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.Workspace; import javax.jcr.nodetype.NodeType; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; import javax.jcr.security.Privilege; import javax.jcr.version.Version; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionIterator; import javax.jcr.version.VersionManager; +import org.apache.commons.io.IOUtils; + /** * Utility class whose purpose is to make using JCR less verbose by * systematically using unchecked exceptions and returning null @@ -94,62 +103,70 @@ public class Jcr { /** * @see Node#isNodeType(String) - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static boolean isNodeType(Node node, String nodeTypeName) { try { return node.isNodeType(nodeTypeName); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get whether " + node + " is of type " + nodeTypeName, e); + throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e); } } /** * @see Node#hasNodes() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static boolean hasNodes(Node node) { try { return node.hasNodes(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get whether " + node + " has children.", e); + throw new JcrException("Cannot get whether " + node + " has children.", e); } } /** * @see Node#getParent() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Node getParent(Node node) { try { return isRoot(node) ? null : node.getParent(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get parent of " + node, e); + throw new JcrException("Cannot get parent of " + node, e); } } + /** + * @see Node#getParent() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getParentPath(Node node) { + return getPath(getParent(node)); + } + /** * Whether this node is the root node. * - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static boolean isRoot(Node node) { try { return node.getDepth() == 0; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get depth of " + node, e); + throw new JcrException("Cannot get depth of " + node, e); } } /** * @see Node#getPath() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static String getPath(Node node) { try { return node.getPath(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get path of " + node, e); + throw new JcrException("Cannot get path of " + node, e); } } @@ -164,25 +181,67 @@ public class Jcr { /** * @see Node#getIdentifier() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static String getIdentifier(Node node) { try { return node.getIdentifier(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get identifier of " + node, e); + throw new JcrException("Cannot get identifier of " + node, e); } } /** * @see Node#getName() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static String getName(Node node) { try { return node.getName(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get name of " + node, e); + throw new JcrException("Cannot get name of " + node, e); + } + } + + /** + * Returns the node name with its current index (useful for re-ordering). + * + * @see Node#getName() + * @see Node#getIndex() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getIndexedName(Node node) { + try { + return node.getName() + "[" + node.getIndex() + "]"; + } catch (RepositoryException e) { + throw new JcrException("Cannot get name of " + node, e); + } + } + + /** + * @see Node#getProperty(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static Property getProperty(Node node, String property) { + try { + if (node.hasProperty(property)) + return node.getProperty(property); + else + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + property + " of " + node, e); + } + } + + /** + * @see Node#getIndex() + * @throws JcrException caused by {@link RepositoryException} + */ + public static int getIndex(Node node) { + try { + return node.getIndex(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get index of " + node, e); } } @@ -212,20 +271,20 @@ public class Jcr { /** * @return the children as an {@link Iterable} for use in for-each llops. * @see Node#getNodes() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Iterable nodes(Node node) { try { return iterate(node.getNodes()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get children of " + node, e); + throw new JcrException("Cannot get children of " + node, e); } } /** * @return the children as a (possibly empty) {@link List}. * @see Node#getNodes() - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static List getNodes(Node node) { List nodes = new ArrayList<>(); @@ -238,14 +297,14 @@ public class Jcr { } else return nodes; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get children of " + node, e); + throw new JcrException("Cannot get children of " + node, e); } } /** * @return the child or null if not found * @see Node#getNode(String) - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Node getNode(Node node, String child) { try { @@ -254,14 +313,14 @@ public class Jcr { else return null; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get child of " + node, e); + throw new JcrException("Cannot get child of " + node, e); } } /** * @return the node at this path or null if not found * @see Session#getNode(String) - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Node getNode(Session session, String path) { try { @@ -270,14 +329,93 @@ public class Jcr { else return null; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get node " + path, e); + throw new JcrException("Cannot get node " + path, e); + } + } + + /** + * Add a node to this parent, setting its primary type and its mixins. + * + * @param parent the parent node + * @param name the name of the node, if null, the primary + * type will be used (typically for XML structures) + * @param primaryType the primary type, if null + * {@link NodeType#NT_UNSTRUCTURED} will be used. + * @param mixins the mixins + * @return the created node + * @see Node#addNode(String, String) + * @see Node#addMixin(String) + */ + public static Node addNode(Node parent, String name, String primaryType, String... mixins) { + if (name == null && primaryType == null) + throw new IllegalArgumentException("Both node name and primary type cannot be null"); + try { + Node newNode = parent.addNode(name == null ? primaryType : name, + primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType); + for (String mixin : mixins) { + newNode.addMixin(mixin); + } + return newNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot add node " + name + " to " + parent, e); + } + } + + /** + * Add an {@link NodeType#NT_BASE} node to this parent. + * + * @param parent the parent node + * @param name the name of the node, cannot be null + * @return the created node + * + * @see Node#addNode(String) + */ + public static Node addNode(Node parent, String name) { + if (name == null) + throw new IllegalArgumentException("Node name cannot be null"); + try { + Node newNode = parent.addNode(name); + return newNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot add node " + name + " to " + parent, e); + } + } + + /** + * Add mixins to a node. + * + * @param node the node + * @param mixins the mixins + * @return the created node + * @see Node#addMixin(String) + */ + public static void addMixin(Node node, String... mixins) { + try { + for (String mixin : mixins) { + node.addMixin(mixin); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e); + } + } + + /** + * Removes this node. + * + * @see Node#remove() + */ + public static void remove(Node node) { + try { + node.remove(); + } catch (RepositoryException e) { + throw new JcrException("Cannot remove node " + node, e); } } /** * @return the node with htis id or null if not found * @see Session#getNodeByIdentifier(String) - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Node getNodeById(Session session, String id) { try { @@ -285,7 +423,7 @@ public class Jcr { } catch (ItemNotFoundException e) { return null; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get node with id " + id, e); + throw new JcrException("Cannot get node with id " + id, e); } } @@ -293,24 +431,62 @@ public class Jcr { * Set a property to the given value, or remove it if the value is * null. * - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static void set(Node node, String property, Object value) { try { - if (!node.hasProperty(property)) - throw new IllegalArgumentException("No property " + property + " in " + node); + if (!node.hasProperty(property)) { + if (value != null) { + if (value instanceof List) {// multiple + List lst = (List) value; + String[] values = new String[lst.size()]; + for (int i = 0; i < lst.size(); i++) { + values[i] = lst.get(i).toString(); + } + node.setProperty(property, values); + } else { + node.setProperty(property, value.toString()); + } + } + return; + } Property prop = node.getProperty(property); if (value == null) { prop.remove(); return; } + // multiple + if (value instanceof List) { + List lst = (List) value; + String[] values = new String[lst.size()]; + // TODO better cast? + for (int i = 0; i < lst.size(); i++) { + values[i] = lst.get(i).toString(); + } + if (!prop.isMultiple()) + prop.remove(); + node.setProperty(property, values); + return; + } + + // single + if (prop.isMultiple()) { + prop.remove(); + node.setProperty(property, value.toString()); + return; + } + if (value instanceof String) prop.setValue((String) value); else if (value instanceof Long) prop.setValue((Long) value); + else if (value instanceof Integer) + prop.setValue(((Integer) value).longValue()); else if (value instanceof Double) prop.setValue((Double) value); + else if (value instanceof Float) + prop.setValue(((Float) value).doubleValue()); else if (value instanceof Calendar) prop.setValue((Calendar) value); else if (value instanceof BigDecimal) @@ -327,7 +503,7 @@ public class Jcr { } else // try with toString() prop.setValue(value.toString()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot set property " + property + " of " + node + " to " + value, e); + throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e); } } @@ -337,7 +513,7 @@ public class Jcr { * @return the value of * {@link Node#getProperty(String)}.{@link Property#getString()} or * null if the property does not exist. - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static String get(Node node, String property) { return get(node, property, null); @@ -350,7 +526,7 @@ public class Jcr { * @return the value of * {@link Node#getProperty(String)}.{@link Property#getString()} or * defaultValue if the property does not exist. - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static String get(Node node, String property, String defaultValue) { try { @@ -368,7 +544,7 @@ public class Jcr { } else return defaultValue; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot retrieve property " + property + " from " + node); + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); } } @@ -377,7 +553,7 @@ public class Jcr { * * @return {@link Node#getProperty(String)} or null if the property * does not exist. - * @throws IllegalStateException caused by {@link RepositoryException} + * @throws JcrException caused by {@link RepositoryException} */ public static Value getValue(Node node, String property) { try { @@ -386,7 +562,7 @@ public class Jcr { else return null; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot retrieve property " + property + " from " + node); + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); } } @@ -396,7 +572,7 @@ public class Jcr { * @return the value of {@link Node#getProperty(String)} or * defaultValue if the property does not exist. * @throws IllegalArgumentException if the value could not be cast - * @throws IllegalStateException in case of unexpected + * @throws JcrException in case of unexpected * {@link RepositoryException} */ @SuppressWarnings("unchecked") @@ -406,20 +582,11 @@ public class Jcr { if (node.hasProperty(property)) { Property p = node.getProperty(property); try { - switch (p.getType()) { - case PropertyType.STRING: - return (T) node.getProperty(property).getString(); - case PropertyType.DOUBLE: - return (T) (Double) node.getProperty(property).getDouble(); - case PropertyType.LONG: - return (T) (Long) node.getProperty(property).getLong(); - case PropertyType.BOOLEAN: - return (T) (Boolean) node.getProperty(property).getBoolean(); - case PropertyType.DATE: - return (T) node.getProperty(property).getDate(); - default: - return (T) node.getProperty(property).getString(); + if (p.isMultiple()) { + throw new UnsupportedOperationException("Multiple values properties are not supported"); } + Value value = p.getValue(); + return (T) get(value); } catch (ClassCastException e) { throw new IllegalArgumentException( "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e); @@ -428,7 +595,87 @@ public class Jcr { return defaultValue; } } catch (RepositoryException e) { - throw new IllegalStateException("Cannot retrieve property " + property + " from " + node); + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); + } + } + + /** + * Get a multiple property as a list, doing a best effort to cast it as the + * target list. + * + * @return the value of {@link Node#getProperty(String)}. + * @throws IllegalArgumentException if the value could not be cast + * @throws JcrException in case of unexpected + * {@link RepositoryException} + */ + public static List getMultiple(Node node, String property) { + try { + if (node.hasProperty(property)) { + Property p = node.getProperty(property); + return getMultiple(p); + } else { + return null; + } + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e); + } + } + + /** + * Get a multiple property as a list, doing a best effort to cast it as the + * target list. + */ + @SuppressWarnings("unchecked") + public static List getMultiple(Property p) { + try { + List res = new ArrayList<>(); + if (!p.isMultiple()) { + res.add((T) get(p.getValue())); + return res; + } + Value[] values = p.getValues(); + for (Value value : values) { + res.add((T) get(value)); + } + return res; + } catch (ClassCastException | RepositoryException e) { + throw new IllegalArgumentException("Cannot get property " + p, e); + } + } + + /** Cast a {@link Value} to a standard Java object. */ + public static Object get(Value value) { + Binary binary = null; + try { + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DOUBLE: + return (Double) value.getDouble(); + case PropertyType.LONG: + return (Long) value.getLong(); + case PropertyType.BOOLEAN: + return (Boolean) value.getBoolean(); + case PropertyType.DATE: + return value.getDate(); + case PropertyType.BINARY: + binary = value.getBinary(); + byte[] arr = null; + try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + IOUtils.copy(in, out); + arr = out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Cannot read binary from " + value, e); + } + return arr; + default: + return value.getString(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot cast value from " + value, e); + } finally { + if (binary != null) + binary.dispose(); } } @@ -447,7 +694,7 @@ public class Jcr { try { return node.getSession(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot retrieve session related to " + node, e); + throw new JcrException("Cannot retrieve session related to " + node, e); } } @@ -456,7 +703,16 @@ public class Jcr { try { return session.getRootNode(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get root node for " + session, e); + throw new JcrException("Cannot get root node for " + session, e); + } + } + + /** Whether this item exists. */ + public static boolean itemExists(Session session, String path) { + try { + return session.itemExists(path); + } catch (RepositoryException e) { + throw new JcrException("Cannot check whether " + path + " exists", e); } } @@ -474,7 +730,7 @@ public class Jcr { if (session.hasPendingChanges()) session.save(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot save session related to " + node + " in workspace " + throw new JcrException("Cannot save session related to " + node + " in workspace " + session(node).getWorkspace().getName(), e); } } @@ -517,7 +773,7 @@ public class Jcr { Session session = node.getSession(); JcrUtils.addPrivilege(session, node.getPath(), principal, privilege); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot add privilege " + privilege + " to " + node, e); + throw new JcrException("Cannot add privilege " + privilege + " to " + node, e); } } @@ -529,7 +785,7 @@ public class Jcr { try { return node.isCheckedOut(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot retrieve checked out status of " + node, e); + throw new JcrException("Cannot retrieve checked out status of " + node, e); } } @@ -538,7 +794,7 @@ public class Jcr { try { versionManager(node).checkpoint(node.getPath()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot check in " + node, e); + throw new JcrException("Cannot check in " + node, e); } } @@ -547,7 +803,7 @@ public class Jcr { try { versionManager(node).checkin(node.getPath()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot check in " + node, e); + throw new JcrException("Cannot check in " + node, e); } } @@ -556,7 +812,7 @@ public class Jcr { try { versionManager(node).checkout(node.getPath()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot check out " + node, e); + throw new JcrException("Cannot check out " + node, e); } } @@ -565,7 +821,7 @@ public class Jcr { try { return node.getSession().getWorkspace().getVersionManager(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get version manager from " + node, e); + throw new JcrException("Cannot get version manager from " + node, e); } } @@ -574,7 +830,7 @@ public class Jcr { try { return versionManager(node).getVersionHistory(node.getPath()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get version history from " + node, e); + throw new JcrException("Cannot get version history from " + node, e); } } @@ -592,7 +848,7 @@ public class Jcr { Collections.reverse(lst); return lst; } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get linear versions from " + versionHistory, e); + throw new JcrException("Cannot get linear versions from " + versionHistory, e); } } @@ -601,7 +857,7 @@ public class Jcr { try { return version.getFrozenNode(); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get frozen node from " + version, e); + throw new JcrException("Cannot get frozen node from " + version, e); } } @@ -610,7 +866,7 @@ public class Jcr { try { return versionManager(node).getBaseVersion(node.getPath()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get base version from " + node, e); + throw new JcrException("Cannot get base version from " + node, e); } } @@ -628,7 +884,7 @@ public class Jcr { throw new IllegalArgumentException(fileNode + " must be a file."); return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()); } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get file size of " + fileNode, e); + throw new JcrException("Cannot get file size of " + fileNode, e); } } @@ -639,8 +895,77 @@ public class Jcr { return binary.getSize(); } } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get file size of binary " + binaryArg, e); + throw new JcrException("Cannot get file size of binary " + binaryArg, e); + } + } + + // QUERY + /** Creates a JCR-SQL2 query using {@link MessageFormat}. */ + public static Query createQuery(QueryManager qm, String sql, Object... args) { + // fix single quotes + sql = sql.replaceAll("'", "''"); + String query = MessageFormat.format(sql, args); + try { + return qm.createQuery(query, Query.JCR_SQL2); + } catch (RepositoryException e) { + throw new JcrException("Cannot create JCR-SQL2 query from " + query, e); + } + } + + /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ + public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) { + Query query = createQuery(qm, sql, args); + try { + return query.execute().getNodes(); + } catch (RepositoryException e) { + throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e); + } + } + + /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ + public static NodeIterator executeQuery(Session session, String sql, Object... args) { + QueryManager queryManager; + try { + queryManager = session.getWorkspace().getQueryManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get query manager from session " + session, e); + } + return executeQuery(queryManager, sql, args); + } + + /** + * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a + * single node at most. + * + * @return the node or null if not found. + */ + public static Node getNode(QueryManager qm, String sql, Object... args) { + NodeIterator nit = executeQuery(qm, sql, args); + if (nit.hasNext()) { + Node node = nit.nextNode(); + if (nit.hasNext()) + throw new IllegalStateException( + "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node."); + return node; + } else { + return null; + } + } + + /** + * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a + * single node at most. + * + * @return the node or null if not found. + */ + public static Node getNode(Session session, String sql, Object... args) { + QueryManager queryManager; + try { + queryManager = session.getWorkspace().getQueryManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get query manager from session " + session, e); } + return getNode(queryManager, sql, args); } /** Singleton. */