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