Introduce JCRX checksum and improve JCR last modified routines.
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 3 Nov 2020 07:45:06 +0000 (08:45 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 3 Nov 2020 07:45:06 +0000 (08:45 +0100)
org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java
org.argeo.jcr/src/org/argeo/jcr/Jcr.java
org.argeo.jcr/src/org/argeo/jcr/JcrUtils.java
org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/JcrxName.java [new file with mode: 0644]
org.argeo.jcr/src/org/argeo/jcr/JcrxType.java
org.argeo.jcr/src/org/argeo/jcr/JcrxUtils.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/QualifiedName.java [deleted file]
org.argeo.jcr/src/org/argeo/jcr/jcrx.cnd
org.argeo.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java

index 3ae2036ae8e221849aff86c9929ddd943b24c394..8f0685280232ddb54cdfc3692f06ba588c42e6fa 100644 (file)
@@ -142,7 +142,7 @@ public class FormPageViewer extends AbstractPageViewer {
                // TODO: make this configurable, sometimes we do not want to save the
                // current session at this stage
                if (node != null && node.getSession().hasPendingChanges()) {
-                       JcrUtils.updateLastModified(node);
+                       JcrUtils.updateLastModified(node, true);
                        node.getSession().save();
                }
        }
index fe73b53f428a44fbc7ec7271757e0c99e40e4ca5..263128448f3f5f133b194c91cf2f647013d5b5d2 100644 (file)
@@ -344,7 +344,8 @@ public class Jcr {
        }
 
        /**
-        * 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
@@ -353,9 +354,18 @@ public class Jcr {
         */
        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);
@@ -392,6 +402,7 @@ public class Jcr {
        @SuppressWarnings("unchecked")
        public static <T> T getAs(Node node, String property, T defaultValue) {
                try {
+                       // TODO deal with multiple
                        if (node.hasProperty(property)) {
                                Property p = node.getProperty(property);
                                try {
index 9dfbf305a89024df372d1c08ce81307f867e20e6..98dce7eb8f4cd0e460caad6dd4933b517dd7acc7 100644 (file)
@@ -13,6 +13,7 @@ import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.text.DateFormat;
 import java.text.ParseException;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
@@ -754,7 +755,7 @@ public class JcrUtils {
 
                        // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
                        // they existed, before adding the mixins
-                       updateLastModified(toNode);
+                       updateLastModified(toNode, true);
 
                        // add mixins
                        for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
@@ -1168,17 +1169,62 @@ public class JcrUtils {
        }
 
        /**
-        * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it updates
-        * the {@link Property#JCR_LAST_MODIFIED} property with the current time and the
-        * {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying session
-        * user id. In Jackrabbit 2.x,
+        * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards)
+        * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}.
+        */
+       public static Instant getModified(Node node) {
+               Calendar calendar = null;
+               try {
+                       if (node.hasProperty(Property.JCR_LAST_MODIFIED))
+                               calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
+                       else if (node.hasProperty(Property.JCR_CREATED))
+                               calendar = node.getProperty(Property.JCR_CREATED).getDate();
+                       else
+                               throw new IllegalArgumentException("No modification time found in " + node);
+                       return calendar.toInstant();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get modification time for " + node, e);
+               }
+
+       }
+
+       /**
+        * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set.
+        */
+       public static Instant getCreated(Node node) {
+               Calendar calendar = null;
+               try {
+                       if (node.hasProperty(Property.JCR_CREATED))
+                               calendar = node.getProperty(Property.JCR_CREATED).getDate();
+                       else
+                               throw new IllegalArgumentException("No created time found in " + node);
+                       return calendar.toInstant();
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get created time for " + node, e);
+               }
+
+       }
+
+       /**
+        * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+        * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+        * session user id.
+        */
+       public static void updateLastModified(Node node) {
+               updateLastModified(node, false);
+       }
+
+       /**
+        * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time
+        * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying
+        * session user id. In Jackrabbit 2.x,
         * <a href="https://issues.apache.org/jira/browse/JCR-2233">these properties are
         * not automatically updated</a>, hence the need for manual update. The session
         * is not saved.
         */
-       public static void updateLastModified(Node node) {
+       public static void updateLastModified(Node node, boolean addMixin) {
                try {
-                       if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+                       if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED))
                                node.addMixin(NodeType.MIX_LAST_MODIFIED);
                        node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar());
                        node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID());
@@ -1193,17 +1239,17 @@ public class JcrUtils {
         * @param node      the node
         * @param untilPath the base path, null is equivalent to "/"
         */
-       public static void updateLastModifiedAndParents(Node node, String untilPath) {
+       public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) {
                try {
                        if (untilPath != null && !node.getPath().startsWith(untilPath))
                                throw new IllegalArgumentException(node + " is not under " + untilPath);
-                       updateLastModified(node);
+                       updateLastModified(node, addMixin);
                        if (untilPath == null) {
                                if (!node.getPath().equals("/"))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                                       updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
                        } else {
                                if (!node.getPath().equals(untilPath))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                                       updateLastModifiedAndParents(node.getParent(), untilPath, addMixin);
                        }
                } catch (RepositoryException e) {
                        throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e);
diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java b/org.argeo.jcr/src/org/argeo/jcr/JcrxApi.java
new file mode 100644 (file)
index 0000000..ab34c73
--- /dev/null
@@ -0,0 +1,161 @@
+package org.argeo.jcr;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/** Uilities around the JCR extensions. */
+public class JcrxApi {
+       public final static String MD5 = "MD5";
+       public final static String SHA1 = "SHA1";
+       public final static String SHA256 = "SHA-256";
+       public final static String SHA512 = "SHA-512";
+
+       public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
+       public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
+       public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+       public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e";
+
+       public final static int LENGTH_MD5 = EMPTY_MD5.length();
+       public final static int LENGTH_SHA1 = EMPTY_SHA1.length();
+       public final static int LENGTH_SHA256 = EMPTY_SHA256.length();
+       public final static int LENGTH_SHA512 = EMPTY_SHA512.length();
+
+       /*
+        * XML
+        */
+       /**
+        * Set as a subnode which will be exported as an XML element.
+        */
+       public static String getXmlValue(Node node, String name) {
+               try {
+                       if (!node.hasNode(name))
+                               throw new IllegalArgumentException("No XML text named " + name);
+                       return node.getNode(name).getNode(Jcr.JCR_XMLTEXT).getProperty(Jcr.JCR_XMLCHARACTERS).getString();
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot get " + name + " as XML text", e);
+               }
+       }
+
+       /**
+        * Set as a subnode which will be exported as an XML element.
+        */
+       public static void setXmlValue(Node node, String name, String value) {
+               try {
+                       if (node.hasNode(name))
+                               node.getNode(name).getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
+                       else
+                               node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
+                                               .setProperty(Jcr.JCR_XMLCHARACTERS, value);
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot set " + name + " as XML text", e);
+               }
+       }
+
+       /**
+        * Add a checksum replacing the one which was previously set with the same
+        * length.
+        */
+       public static void addChecksum(Node node, String checksum) {
+               try {
+                       if (!node.hasProperty(JcrxName.JCRX_SUM)) {
+                               node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum });
+                               return;
+                       } else {
+                               int stringLength = checksum.length();
+                               Property property = node.getProperty(JcrxName.JCRX_SUM);
+                               List<Value> values = Arrays.asList(property.getValues());
+                               Integer indexToRemove = null;
+                               values: for (int i = 0; i < values.size(); i++) {
+                                       Value value = values.get(i);
+                                       if (value.getString().length() == stringLength) {
+                                               indexToRemove = i;
+                                               break values;
+                                       }
+                               }
+                               if (indexToRemove != null)
+                                       values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum));
+                               else
+                                       values.add(0, node.getSession().getValueFactory().createValue(checksum));
+                               property.setValue(values.toArray(new Value[values.size()]));
+                       }
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot set checksum on " + node, e);
+               }
+       }
+
+       /** Replace all checksums. */
+       public static void setChecksums(Node node, List<String> checksums) {
+               try {
+                       node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()]));
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot set checksums on " + node, e);
+               }
+       }
+
+       /** Replace all checksums. */
+       public static List<String> getChecksums(Node node) {
+               try {
+                       List<String> res = new ArrayList<>();
+                       if (!node.hasProperty(JcrxName.JCRX_SUM))
+                               return res;
+                       Property property = node.getProperty(JcrxName.JCRX_SUM);
+                       for (Value value : property.getValues()) {
+                               res.add(value.getString());
+                       }
+                       return res;
+               } catch (RepositoryException e) {
+                       throw new JcrException("Cannot get checksums from " + node, e);
+               }
+       }
+
+//     /** Replace all checksums with this single one. */
+//     public static void setChecksum(Node node, String checksum) {
+//             setChecksums(node, Collections.singletonList(checksum));
+//     }
+
+       /** Retrieves the checksum with this algorithm, or null if not found. */
+       public static String getChecksum(Node node, String algorithm) {
+               int stringLength;
+               switch (algorithm) {
+               case MD5:
+                       stringLength = LENGTH_MD5;
+                       break;
+               case SHA1:
+                       stringLength = LENGTH_SHA1;
+                       break;
+               case SHA256:
+                       stringLength = LENGTH_SHA256;
+                       break;
+               case SHA512:
+                       stringLength = LENGTH_SHA512;
+                       break;
+               default:
+                       throw new IllegalArgumentException("Unkown algorithm " + algorithm);
+               }
+               return getChecksum(node, stringLength);
+       }
+
+       /** Retrieves the checksum with this string length, or null if not found. */
+       public static String getChecksum(Node node, int stringLength) {
+               try {
+                       if (!node.hasProperty(JcrxName.JCRX_SUM))
+                               return null;
+                       Property property = node.getProperty(JcrxName.JCRX_SUM);
+                       for (Value value : property.getValues()) {
+                               String str = value.getString();
+                               if (str.length() == stringLength)
+                                       return str;
+                       }
+                       return null;
+               } catch (RepositoryException e) {
+                       throw new IllegalStateException("Cannot get checksum for " + node, e);
+               }
+       }
+
+}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrxName.java b/org.argeo.jcr/src/org/argeo/jcr/JcrxName.java
new file mode 100644 (file)
index 0000000..9dd43ad
--- /dev/null
@@ -0,0 +1,7 @@
+package org.argeo.jcr;
+
+/** Names declared by the JCR extensions. */
+public interface JcrxName {
+       /** The multiple property holding various coherent checksums. */
+       public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum";
+}
index 984a739aa7b48c48a6d3b8acb8132b9bec9e7120..0cbad3341d784d8630f67196edf856dae9dc30c9 100644 (file)
@@ -11,4 +11,7 @@ public interface JcrxType {
        /** Node type for the node containing the text. */
        public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext";
 
+       /** Mixin node type for a set of checksums. */
+       public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum";
+
 }
diff --git a/org.argeo.jcr/src/org/argeo/jcr/JcrxUtils.java b/org.argeo.jcr/src/org/argeo/jcr/JcrxUtils.java
deleted file mode 100644 (file)
index d5f8f18..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package org.argeo.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-/** Uilities around the JCR extensions. */
-public class JcrxUtils {
-
-       /*
-        * XML
-        */
-       /**
-        * Set as a subnode which will be exported as an XML element.
-        */
-       public static String getXmlValue(Node node, String name) {
-               try {
-                       if (!node.hasNode(name))
-                               throw new IllegalArgumentException("No XML text named " + name);
-                       return node.getNode(name).getNode(Jcr.JCR_XMLTEXT).getProperty(Jcr.JCR_XMLCHARACTERS).getString();
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot get " + name + " as XML text", e);
-               }
-       }
-
-       /**
-        * Set as a subnode which will be exported as an XML element.
-        */
-       public static void setXmlValue(Node node, String name, String value) {
-               try {
-                       if (node.hasNode(name))
-                               node.getNode(name).getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value);
-                       else
-                               node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT)
-                                               .setProperty(Jcr.JCR_XMLCHARACTERS, value);
-               } catch (RepositoryException e) {
-                       throw new IllegalStateException("Cannot set " + name + " as XML text", e);
-               }
-       }
-
-}
diff --git a/org.argeo.jcr/src/org/argeo/jcr/QualifiedName.java b/org.argeo.jcr/src/org/argeo/jcr/QualifiedName.java
deleted file mode 100644 (file)
index 419f74e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-package org.argeo.jcr;
-
-/** Can be applied to {@link Enum}s in order to generate prefixed names. */
-public interface QualifiedName {
-       String name();
-
-       default String getPrefix() {
-               return null;
-       }
-
-       default String getNamespace() {
-               return null;
-       }
-
-       default String qualified() {
-               String prefix = getPrefix();
-               return prefix != null ? prefix + ":" + name() : name();
-       }
-
-       default String withNamespace() {
-               String namespace = getNamespace();
-               if (namespace == null)
-                       throw new UnsupportedOperationException("No namespace is specified for " + getClass());
-               return "{" + namespace + "}" + name();
-       }
-}
index c8c44ad4ae538813a76b2693a4c49b2be90ebbcc..3eb0e7a3d90b17f941466a7c4bfbfb07c9c52630 100644 (file)
@@ -9,3 +9,8 @@
 
 [jcrx:xmltext]
  - jcr:xmlcharacters (STRING) mandatory
+
+[jcrx:csum]
+mixin
+ - jcrx:sum (STRING) *
\ No newline at end of file
index f0e8605c403a0472cba81d889e55efbccdf7463e..0984276ddbb90c810d824fde99e3082e81560c17 100644 (file)
@@ -131,7 +131,7 @@ public abstract class AbstractUrlProxy implements ResourceProxy {
                        }
                        binary = session.getValueFactory().createBinary(in);
                        content.setProperty(Property.JCR_DATA, binary);
-                       JcrUtils.updateLastModifiedAndParents(node, null);
+                       JcrUtils.updateLastModifiedAndParents(node, null, true);
                        return node;
                } finally {
                        JcrUtils.closeQuietly(binary);