Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (revision 1908)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLexicon.java (working copy)
@@ -39,6 +39,7 @@ public class JcrLexicon extends org.modeshape.graph.JcrLexicon {
public static final Name COPIED_FROM = new BasicName(Namespace.URI, "copiedFrom");
public static final Name DATA = new BasicName(Namespace.URI, "data");
public static final Name ENCODING = new BasicName(Namespace.URI, "encoding");
+ public static final Name ETAG = new BasicName(Namespace.URI, "etag");
public static final Name FROZEN_MIXIN_TYPES = new BasicName(Namespace.URI, "frozenMixinTypes");
public static final Name FROZEN_NODE = new BasicName(Namespace.URI, "frozenNode");
public static final Name FROZEN_PRIMARY_TYPE = new BasicName(Namespace.URI, "frozenPrimaryType");
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java (revision 1908)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrMixLexicon.java (working copy)
@@ -24,6 +24,8 @@
package org.modeshape.jcr;
import net.jcip.annotations.Immutable;
+import org.modeshape.graph.property.Name;
+import org.modeshape.graph.property.basic.BasicName;
/**
* Lexicon of names from the standard JCR "http://www.jcp.org/jcr/mix/1.0
" namespace.
@@ -31,4 +33,6 @@ import net.jcip.annotations.Immutable;
@Immutable
public class JcrMixLexicon extends org.modeshape.graph.JcrMixLexicon {
+ public static final Name ETAG = new BasicName(Namespace.URI, "etag");
+
}
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (revision 1908)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (working copy)
@@ -60,6 +60,8 @@ import org.modeshape.graph.ExecutionContext;
import org.modeshape.graph.Graph;
import org.modeshape.graph.Location;
import org.modeshape.graph.connector.RepositorySourceException;
+import org.modeshape.graph.property.Binary;
+import org.modeshape.graph.property.BinaryFactory;
import org.modeshape.graph.property.DateTime;
import org.modeshape.graph.property.Name;
import org.modeshape.graph.property.NameFactory;
@@ -2644,6 +2646,7 @@ class SessionCache {
JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName);
boolean isLastModifiedType = primaryType.isNodeType(JcrMixLexicon.LAST_MODIFIED);
boolean isCreatedType = primaryType.isNodeType(JcrMixLexicon.CREATED);
+ boolean isETag = primaryType.isNodeType(JcrMixLexicon.ETAG);
for (JcrPropertyDefinition definition : primaryType.getPropertyDefinitions()) {
if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) {
throw new ValidationException(JcrI18n.noDefinition.text("property",
@@ -2668,6 +2671,7 @@ class SessionCache {
JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName);
isLastModifiedType = isLastModifiedType || mixinType.isNodeType(JcrMixLexicon.LAST_MODIFIED);
isCreatedType = isCreatedType || mixinType.isNodeType(JcrMixLexicon.CREATED);
+ isETag = isETag || mixinType.isNodeType(JcrMixLexicon.ETAG);
for (JcrPropertyDefinition definition : mixinType.getPropertyDefinitions()) {
if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) {
throw new ValidationException(JcrI18n.noDefinition.text("child node",
@@ -2690,6 +2694,43 @@ class SessionCache {
}
}
+ // Do we need to update the 'jcr:etag' property?
+ if (isETag) {
+ // Per section 3.7.12 of JCR 2, this property should be changed whenever BINARY properties are added, removed, or
+ // changed. So, go through the properties (in sorted-name order so it is repeatable) and create this value
+ // by simply concatenating the SHA-1 hash of each BINARY value ...
+ List binaryPropertyNames = new ArrayList();
+ for (org.modeshape.graph.session.GraphSession.PropertyInfo property : node.getProperties()) {
+ if (property.getProperty().size() == 0) continue;
+ if (property.getPayload().getPropertyType() != PropertyType.BINARY) continue;
+ binaryPropertyNames.add(property.getName());
+ }
+ if (!binaryPropertyNames.isEmpty()) {
+ Collections.sort(binaryPropertyNames);
+ BinaryFactory binaryFactory = context().getValueFactories().getBinaryFactory();
+ StringBuilder sb = new StringBuilder();
+ for (Name name : binaryPropertyNames) {
+ org.modeshape.graph.session.GraphSession.PropertyInfo property = node.getProperty(name);
+ for (Object value : property.getProperty()) {
+ Binary binary = binaryFactory.create(value);
+ String hash = new String(binary.getHash()); // doesn't matter what charset, as long as its always the
+ // same
+ sb.append(hash);
+ }
+ }
+ if (sb.length() != 0) {
+ String etagValue = sb.toString();
+ setPropertyIfAbsent(node,
+ primaryTypeName,
+ mixinTypeNames,
+ false,
+ JcrLexicon.ETAG,
+ PropertyType.STRING,
+ etagValue);
+ }
+ }
+ }
+
// See if the node is an instance of 'mix:created'.
// This is done even if the node is not newly-created, because this needs to happen whenever the
// 'mix:created' node type is added as a mixin (which can happen to an existing node).
Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_283_builtins.cnd
===================================================================
--- modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_283_builtins.cnd (revision 1908)
+++ modeshape-jcr/src/main/resources/org/modeshape/jcr/jsr_283_builtins.cnd (working copy)
@@ -163,6 +163,9 @@
// Pre-defined Mixins
// ------------------------------------------------------------------------
+[mix:etag] mixin
+ - jcr:etag (string) protected autocreated
+
[mix:lockable] mixin
- jcr:lockOwner (string) protected ignore
- jcr:lockIsDeep (boolean) protected ignore