// 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();
}
}
}
/**
- * 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
*/
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);
@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 {
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;
// 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()) {
}
/**
- * 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());
* @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);
--- /dev/null
+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);
+ }
+ }
+
+}
--- /dev/null
+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";
+}
/** 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";
+
}
+++ /dev/null
-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);
- }
- }
-
-}
+++ /dev/null
-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();
- }
-}
[jcrx:xmltext]
- jcr:xmlcharacters (STRING) mandatory
+
+[jcrx:csum]
+mixin
+ - jcrx:sum (STRING) *
+
\ No newline at end of file
}
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);