X-Git-Url: https://git.argeo.org/?a=blobdiff_plain;f=org.argeo.jcr%2Fsrc%2Forg%2Fargeo%2Fjcr%2FJcr.java;h=6f0e39d8298759db33affea0dc57d9a69b7bb47f;hb=93989c5786472b5fad58aec868fcfd1cd07059da;hp=f8ad9c79cca88dd58526b0320612940e61993c8d;hpb=25e4528153640a2e211e217468f8f5aa01607cf0;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 f8ad9c79c..6f0e39d82 100644 --- a/org.argeo.jcr/src/org/argeo/jcr/Jcr.java +++ b/org.argeo.jcr/src/org/argeo/jcr/Jcr.java @@ -1,9 +1,16 @@ package org.argeo.jcr; +import java.math.BigDecimal; +import java.time.Instant; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; +import javax.jcr.Binary; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -13,6 +20,13 @@ import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; +import javax.jcr.Workspace; +import javax.jcr.nodetype.NodeType; +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; /** * Utility class whose purpose is to make using JCR less verbose by @@ -22,92 +36,167 @@ import javax.jcr.Value; * exceptions. Loosely inspired by Java's Files singleton. */ public class Jcr { + /** + * The name of a node which will be serialized as XML text, as per section 7.3.1 + * of the JCR 2.0 specifications. + */ + public final static String JCR_XMLTEXT = "jcr:xmltext"; + /** + * The name of a property which will be serialized as XML text, as per section + * 7.3.1 of the JCR 2.0 specifications. + */ + public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters"; + /** + * jcr:name, when used in another context than + * {@link Property#JCR_NAME}, typically to name a node rather than a property. + */ + public final static String JCR_NAME = "jcr:name"; + /** + * jcr:path, when used in another context than + * {@link Property#JCR_PATH}, typically to name a node rather than a property. + */ + public final static String JCR_PATH = "jcr:path"; + /** + * jcr:primaryType with prefix instead of namespace (as in + * {@link Property#JCR_PRIMARY_TYPE}. + */ + public final static String JCR_PRIMARY_TYPE = "jcr:primaryType"; + /** + * jcr:mixinTypes with prefix instead of namespace (as in + * {@link Property#JCR_MIXIN_TYPES}. + */ + public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; + /** + * jcr:uuid with prefix instead of namespace (as in + * {@link Property#JCR_UUID}. + */ + public final static String JCR_UUID = "jcr:uuid"; + /** + * jcr:created with prefix instead of namespace (as in + * {@link Property#JCR_CREATED}. + */ + public final static String JCR_CREATED = "jcr:created"; + /** + * jcr:createdBy with prefix instead of namespace (as in + * {@link Property#JCR_CREATED_BY}. + */ + public final static String JCR_CREATED_BY = "jcr:createdBy"; + /** + * jcr:lastModified with prefix instead of namespace (as in + * {@link Property#JCR_LAST_MODIFIED}. + */ + public final static String JCR_LAST_MODIFIED = "jcr:lastModified"; + /** + * jcr:lastModifiedBy with prefix instead of namespace (as in + * {@link Property#JCR_LAST_MODIFIED_BY}. + */ + public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; /** * @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); } } /** * 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); } } + /** + * @see Node#getSession() + * @see Session#getWorkspace() + * @see Workspace#getName() + */ + public static String getWorkspaceName(Node node) { + return session(node).getWorkspace().getName(); + } + /** * @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); } } + /** + * If node has mixin {@link NodeType#MIX_TITLE}, return + * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}. + */ + public static String getTitle(Node node) { + if (Jcr.isNodeType(node, NodeType.MIX_TITLE)) + return get(node, Property.JCR_TITLE); + else + return Jcr.getName(node); + } + /** Accesses a {@link NodeIterator} as an {@link Iterable}. */ @SuppressWarnings("unchecked") public static Iterable iterate(NodeIterator nodeIterator) { @@ -123,20 +212,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<>(); @@ -149,14 +238,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 { @@ -165,14 +254,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 { @@ -181,14 +270,14 @@ 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); } } /** * @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 { @@ -196,7 +285,58 @@ 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); + } + } + + /** + * Set a property to the given value, or remove it if the value is + * null. + * + * @throws JcrException caused by {@link RepositoryException} + */ + public static void set(Node node, String property, Object value) { + try { + if (!node.hasProperty(property)) { + if (value != null) + node.setProperty(property, value.toString()); + return; + // throw new IllegalArgumentException("No property " + property + " in " + + // node); + } + Property prop = node.getProperty(property); + if (value == null) { + prop.remove(); + 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) + prop.setValue((BigDecimal) value); + else if (value instanceof Boolean) + prop.setValue((Boolean) value); + else if (value instanceof byte[]) + JcrUtils.setBinaryAsBytes(prop, (byte[]) value); + else if (value instanceof Instant) { + Instant instant = (Instant) value; + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(Date.from(instant)); + prop.setValue(calendar); + } else // try with toString() + prop.setValue(value.toString()); + } catch (RepositoryException e) { + throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e); } } @@ -206,28 +346,38 @@ 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); } /** - * Get property as a {@link String}. + * Get property as a {@link String}. If the property is multiple it returns the + * first value. * * @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 { - if (node.hasProperty(property)) - return node.getProperty(property).getString(); - else + if (node.hasProperty(property)) { + Property p = node.getProperty(property); + if (!p.isMultiple()) + return p.getString(); + else { + Value[] values = p.getValues(); + if (values.length == 0) + return defaultValue; + else + return values[0].getString(); + } + } 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); } } @@ -236,7 +386,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 { @@ -245,7 +395,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); } } @@ -255,12 +405,13 @@ 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") public static T getAs(Node node, String property, T defaultValue) { try { + // TODO deal with multiple if (node.hasProperty(property)) { Property p = node.getProperty(property); try { @@ -286,7 +437,63 @@ 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); + } + } + + /** + * Retrieves the {@link Session} related to this node. + * + * @deprecated Use {@link #getSession(Node)} instead. + */ + @Deprecated + public static Session session(Node node) { + return getSession(node); + } + + /** Retrieves the {@link Session} related to this node. */ + public static Session getSession(Node node) { + try { + return node.getSession(); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve session related to " + node, e); + } + } + + /** Retrieves the root node related to this session. */ + public static Node getRootNode(Session session) { + try { + return session.getRootNode(); + } catch (RepositoryException 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); + } + } + + /** + * Saves the {@link Session} related to this node. Note that all other unrelated + * modifications in this session will also be saved. + */ + public static void save(Node node) { + try { + Session session = node.getSession(); +// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { +// set(node, Property.JCR_LAST_MODIFIED, Instant.now()); +// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID()); +// } + if (session.hasPendingChanges()) + session.save(); + } catch (RepositoryException e) { + throw new JcrException("Cannot save session related to " + node + " in workspace " + + session(node).getWorkspace().getName(), e); } } @@ -310,6 +517,150 @@ public class Jcr { } } + /** Safely and silently logs out the underlying session. */ + public static void logout(Node node) { + Jcr.logout(session(node)); + } + + /* + * SECURITY + */ + /** + * Add a single privilege to a node. + * + * @see Privilege + */ + public static void addPrivilege(Node node, String principal, String privilege) { + try { + Session session = node.getSession(); + JcrUtils.addPrivilege(session, node.getPath(), principal, privilege); + } catch (RepositoryException e) { + throw new JcrException("Cannot add privilege " + privilege + " to " + node, e); + } + } + + /* + * VERSIONING + */ + /** Get checked out status. */ + public static boolean isCheckedOut(Node node) { + try { + return node.isCheckedOut(); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve checked out status of " + node, e); + } + } + + /** @see VersionManager#checkpoint(String) */ + public static void checkpoint(Node node) { + try { + versionManager(node).checkpoint(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check in " + node, e); + } + } + + /** @see VersionManager#checkin(String) */ + public static void checkin(Node node) { + try { + versionManager(node).checkin(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check in " + node, e); + } + } + + /** @see VersionManager#checkout(String) */ + public static void checkout(Node node) { + try { + versionManager(node).checkout(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check out " + node, e); + } + } + + /** Get the {@link VersionManager} related to this node. */ + public static VersionManager versionManager(Node node) { + try { + return node.getSession().getWorkspace().getVersionManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get version manager from " + node, e); + } + } + + /** Get the {@link VersionHistory} related to this node. */ + public static VersionHistory getVersionHistory(Node node) { + try { + return versionManager(node).getVersionHistory(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get version history from " + node, e); + } + } + + /** + * The linear versions of this version history in reverse order and without the + * root version. + */ + public static List getLinearVersions(VersionHistory versionHistory) { + try { + List lst = new ArrayList<>(); + VersionIterator vit = versionHistory.getAllLinearVersions(); + while (vit.hasNext()) + lst.add(vit.nextVersion()); + lst.remove(0); + Collections.reverse(lst); + return lst; + } catch (RepositoryException e) { + throw new JcrException("Cannot get linear versions from " + versionHistory, e); + } + } + + /** The frozen node related to this {@link Version}. */ + public static Node getFrozenNode(Version version) { + try { + return version.getFrozenNode(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get frozen node from " + version, e); + } + } + + /** Get the base {@link Version} related to this node. */ + public static Version getBaseVersion(Node node) { + try { + return versionManager(node).getBaseVersion(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get base version from " + node, e); + } + } + + /* + * FILES + */ + /** + * Returns the size of this file. + * + * @see NodeType#NT_FILE + */ + public static long getFileSize(Node fileNode) { + try { + if (!fileNode.isNodeType(NodeType.NT_FILE)) + 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 JcrException("Cannot get file size of " + fileNode, e); + } + } + + /** Returns the size of this {@link Binary}. */ + public static long getBinarySize(Binary binaryArg) { + try { + try (Bin binary = new Bin(binaryArg)) { + return binary.getSize(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot get file size of binary " + binaryArg, e); + } + } + /** Singleton. */ private Jcr() {