Index: modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (revision 1905) +++ modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (working copy) @@ -897,10 +897,11 @@ public class GraphSession { // Make sure that each of the changed node is valid. This process requires that all children of // all changed nodes are loaded, so in this process load all unloaded children in one batch ... + final DateTime saveTime = context.getValueFactories().getDateFactory().create(); root.onChangedNodes(new LoadAllChildrenVisitor() { @Override protected void finishParentAfterLoading( Node node ) { - nodeOperations.preSave(node); + nodeOperations.preSave(node, saveTime); } }); @@ -973,10 +974,11 @@ public class GraphSession { // Make sure that each of the changed node is valid. This process requires that all children of // all changed nodes are loaded, so in this process load all unloaded children in one batch ... + final DateTime saveTime = context.getValueFactories().getDateFactory().create(); root.onChangedNodes(new LoadAllChildrenVisitor() { @Override protected void finishParentAfterLoading( Node node ) { - nodeOperations.preSave(node); + nodeOperations.preSave(node, saveTime); } }); @@ -1232,9 +1234,11 @@ public class GraphSession { * Validate a node for consistency and well-formedness. * * @param node the node to be validated + * @param saveTime the time at which the save operation is occurring; never null * @throws ValidationException if there is a problem during validation */ - void preSave( Node node ) throws ValidationException; + void preSave( Node node, + DateTime saveTime ) throws ValidationException; /** * Update any computed fields based on the given node @@ -1350,9 +1354,10 @@ public class GraphSession { /** * {@inheritDoc} * - * @see GraphSession.Operations#preSave(GraphSession.Node) + * @see GraphSession.Operations#preSave(GraphSession.Node,DateTime) */ - public void preSave( Node node ) throws ValidationException { + public void preSave( Node node, + DateTime saveTime ) throws ValidationException { // do nothing here } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (revision 1905) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (working copy) @@ -60,6 +60,7 @@ 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.DateTime; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NameFactory; import org.modeshape.graph.property.NamespaceRegistry; @@ -982,14 +983,6 @@ class SessionCache { JcrValue value, boolean skipProtected ) throws AccessDeniedException, ConstraintViolationException, VersionException, RepositoryException { - return setProperty(name, value, skipProtected, false); - } - - protected AbstractJcrProperty setProperty( Name name, - JcrValue value, - boolean skipProtected, - boolean skipLastUpdated ) - throws AccessDeniedException, ConstraintViolationException, VersionException, RepositoryException { assert name != null; assert value != null; @@ -1088,11 +1081,6 @@ class SessionCache { assert jcrProp != null; JcrPropertyPayload propPayload = new JcrPropertyPayload(definition.getId(), propertyType, jcrProp); node.setProperty(dnaProp, definition.isMultiple(), propPayload); - - if (!skipLastUpdated) { - // Update the lastModified properties ... - updateLastModifiedOn(node); - } return jcrProp; } catch (ValidationException e) { throw new ConstraintViolationException(e.getMessage(), e); @@ -1152,16 +1140,6 @@ class SessionCache { boolean skipProtected ) throws AccessDeniedException, ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException, VersionException { - return setProperty(name, values, valueType, skipProtected, false); - } - - protected AbstractJcrProperty setProperty( Name name, - Value[] values, - int valueType, - boolean skipProtected, - boolean skipLastUpdated ) - throws AccessDeniedException, ConstraintViolationException, RepositoryException, javax.jcr.ValueFormatException, - VersionException { assert name != null; assert values != null; @@ -1313,10 +1291,6 @@ class SessionCache { assert jcrProp != null; JcrPropertyPayload propPayload = new JcrPropertyPayload(definition.getId(), propertyType, jcrProp); node.setProperty(dnaProp, definition.isMultiple(), propPayload); - if (!skipLastUpdated) { - // Update the lastModified properties on the node ... - updateLastModifiedOn(node); - } return jcrProp; } catch (ValidationException e) { throw new ConstraintViolationException(e.getMessage(), e); @@ -1337,8 +1311,6 @@ class SessionCache { */ public boolean removeProperty( Name name ) throws AccessDeniedException, RepositoryException { try { - // Update the lastModified properties on the node ... - updateLastModifiedOn(node); return node.removeProperty(name) != null; } catch (ValidationException e) { throw new ConstraintViolationException(e.getMessage(), e); @@ -1363,8 +1335,6 @@ class SessionCache { Path.Segment before ) throws AccessDeniedException, RepositoryException { try { node.orderChildBefore(childToBeMoved, before); - // Update the lastModified properties on the parent ... - updateLastModifiedOn(node); } catch (ValidationException e) { throw new ConstraintViolationException(e.getMessage(), e); } catch (RepositorySourceException e) { @@ -1425,9 +1395,6 @@ class SessionCache { newChildEditor.removeProperty(ModeShapeIntLexicon.NODE_DEFINITON); } } - // Update the lastModified properties on the old and new parent ... - updateLastModifiedOn(existingChild.getParent()); - updateLastModifiedOn(node); return existingChild; } catch (ValidationException e) { @@ -1496,28 +1463,8 @@ class SessionCache { setProperty(propertyDefinition.getInternalName(), (JcrValue)propertyDefinition.getDefaultValues()[0]); } - } else { - JcrNodeType definingNodeType = propertyDefinition.declaringNodeType; - Name name = definingNodeType.getInternalName(); - if (name.equals(JcrMixLexicon.CREATED)) { - JcrNode jcrNode = (JcrNode)node.getPayload().getJcrNode(); - JcrValue now = jcrNode.valueFrom(Calendar.getInstance()); - JcrValue by = jcrNode.valueFrom(session().getUserID()); - setProperty(JcrLexicon.CREATED, now, false, true); - setProperty(JcrLexicon.CREATED_BY, by, false, true); - } else if (name.equals(JcrMixLexicon.LAST_MODIFIED)) { - JcrNode jcrNode = (JcrNode)node.getPayload().getJcrNode(); - JcrValue now = jcrNode.valueFrom(Calendar.getInstance()); - JcrValue by = jcrNode.valueFrom(session().getUserID()); - setProperty(JcrLexicon.LAST_MODIFIED, now, false, true); - setProperty(JcrLexicon.LAST_MODIFIED_BY, by, false, true); - } else if (name.equals(JcrNtLexicon.HIERARCHY_NODE)) { - JcrNode jcrNode = (JcrNode)node.getPayload().getJcrNode(); - JcrValue now = jcrNode.valueFrom(Calendar.getInstance()); - setProperty(JcrLexicon.CREATED, now, false, true); - } - // otherwise, we don't care } + // otherwise, we don't care } } } @@ -1655,26 +1602,18 @@ class SessionCache { JcrValue now = jcrNode.valueFrom(Calendar.getInstance()); JcrValue by = jcrNode.valueFrom(session().getUserID()); boolean isCreatedType = primaryType.isNodeType(JcrMixLexicon.CREATED); - boolean isLastModifiedType = primaryType.isNodeType(JcrMixLexicon.LAST_MODIFIED); boolean isHierarchyNode = primaryType.isNodeType(JcrNtLexicon.HIERARCHY_NODE); - if (isHierarchyNode || isCreatedType || isLastModifiedType) { + if (isHierarchyNode || isCreatedType) { NodeEditor editor = jcrNode.editor(); if (isHierarchyNode) { - editor.setProperty(JcrLexicon.CREATED, now, false, true); + editor.setProperty(JcrLexicon.CREATED, now, false); } if (isCreatedType) { - editor.setProperty(JcrLexicon.CREATED, now, false, true); - editor.setProperty(JcrLexicon.CREATED_BY, by, false, true); - } - if (isLastModifiedType) { - editor.setProperty(JcrLexicon.LAST_MODIFIED, now, false, true); - editor.setProperty(JcrLexicon.LAST_MODIFIED_BY, by, false, true); + editor.setProperty(JcrLexicon.CREATED, now, false); + editor.setProperty(JcrLexicon.CREATED_BY, by, false); } } - // Update the lastModified properties on the parent ... - updateLastModifiedOn(node, now, by); - // The postCreateChild hook impl should populate the payloads jcrNode.editor().autoCreateItemsFor(primaryType); @@ -1689,45 +1628,6 @@ class SessionCache { } } - protected boolean updateLastModifiedOn( Node node ) throws RepositoryException { - if (isNodeType(node, JcrMixLexicon.LAST_MODIFIED)) { - JcrNode jcrNode = (JcrNode)node.getPayload().getJcrNode(); - JcrValue now = jcrNode.valueFrom(Calendar.getInstance()); - JcrValue by = jcrNode.valueFrom(session().getUserID()); - NodeEditor editor = jcrNode.editor(); - editor.setProperty(JcrLexicon.LAST_MODIFIED, now, false, true); - editor.setProperty(JcrLexicon.LAST_MODIFIED_BY, by, false, true); - return true; - } - return false; - } - - protected boolean updateLastModifiedOn( Node node, - JcrValue now, - JcrValue by ) throws RepositoryException { - if (isNodeType(node, JcrMixLexicon.LAST_MODIFIED)) { - JcrNode jcrNode = (JcrNode)node.getPayload().getJcrNode(); - NodeEditor editor = jcrNode.editor(); - editor.setProperty(JcrLexicon.LAST_MODIFIED, now, false, true); - editor.setProperty(JcrLexicon.LAST_MODIFIED_BY, by, false, true); - return true; - } - return false; - } - - protected boolean updateCreatedOn( Node node, - JcrValue now, - JcrValue by ) throws RepositoryException { - if (isNodeType(node, JcrMixLexicon.CREATED)) { - JcrNode parent = (JcrNode)node.getPayload().getJcrNode(); - NodeEditor editor = parent.editor(); - editor.setProperty(JcrLexicon.CREATED, now, false, true); - editor.setProperty(JcrLexicon.CREATED_BY, by, false, true); - return true; - } - return false; - } - /** * Destroy the child node with the supplied UUID and all nodes that exist below it, including any nodes that were created * and haven't been persisted. @@ -1741,8 +1641,6 @@ class SessionCache { throws AccessDeniedException, RepositoryException { if (!child.getParent().equals(node)) return false; try { - // Update the lastModified properties on the parent ... - updateLastModifiedOn(node); child.destroy(); } catch (AccessControlException e) { throw new AccessDeniedException(e.getMessage(), e); @@ -2333,6 +2231,7 @@ class SessionCache { @Immutable final class JcrNodeOperations extends GraphSession.NodeOperations { private final Logger LOGGER = Logger.getLogger(JcrNodeOperations.class); + private final String user = SessionCache.this.session().getUserID(); private Map> buildProperties( org.modeshape.graph.Node persistentNode, Node node, @@ -2684,11 +2583,12 @@ class SessionCache { /** * {@inheritDoc} * - * @see org.modeshape.graph.session.GraphSession.Operations#preSave(org.modeshape.graph.session.GraphSession.Node) + * @see org.modeshape.graph.session.GraphSession.NodeOperations#preSave(org.modeshape.graph.session.GraphSession.Node, + * org.modeshape.graph.property.DateTime) */ @Override - public void preSave( org.modeshape.graph.session.GraphSession.Node node ) - throws ValidationException { + public void preSave( org.modeshape.graph.session.GraphSession.Node node, + DateTime saveTime ) throws ValidationException { JcrNodePayload payload = node.getPayload(); Name primaryTypeName = payload.getPrimaryTypeName(); @@ -2742,6 +2642,8 @@ class SessionCache { } JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName); + boolean isLastModifiedType = primaryType.isNodeType(JcrMixLexicon.LAST_MODIFIED); + boolean isCreatedType = primaryType.isNodeType(JcrMixLexicon.CREATED); for (JcrPropertyDefinition definition : primaryType.getPropertyDefinitions()) { if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) { throw new ValidationException(JcrI18n.noDefinition.text("property", @@ -2764,6 +2666,8 @@ class SessionCache { if (mixinTypeNames != null) { for (Name mixinTypeName : mixinTypeNames) { JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName); + isLastModifiedType = isLastModifiedType || mixinType.isNodeType(JcrMixLexicon.LAST_MODIFIED); + isCreatedType = isCreatedType || mixinType.isNodeType(JcrMixLexicon.CREATED); for (JcrPropertyDefinition definition : mixinType.getPropertyDefinitions()) { if (definition.isMandatory() && !definition.isProtected() && !satisfiedProperties.contains(definition)) { throw new ValidationException(JcrI18n.noDefinition.text("child node", @@ -2785,6 +2689,60 @@ class SessionCache { } } + + // 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). + if (isCreatedType) { + setPropertyIfAbsent(node, primaryTypeName, mixinTypeNames, false, JcrLexicon.CREATED, PropertyType.DATE, saveTime); + setPropertyIfAbsent(node, + primaryTypeName, + mixinTypeNames, + false, + JcrLexicon.CREATED_BY, + PropertyType.STRING, + user); + } + + // See if the node is an instance of 'mix:lastModified' ... + if (isLastModifiedType) { + // Check to see if the 'jcr:lastModified' or 'jcr:lastModifiedBy' properties were explicitly changed ... + setPropertyIfAbsent(node, + primaryTypeName, + mixinTypeNames, + false, + JcrLexicon.LAST_MODIFIED, + PropertyType.DATE, + saveTime); + setPropertyIfAbsent(node, + primaryTypeName, + mixinTypeNames, + false, + JcrLexicon.LAST_MODIFIED_BY, + PropertyType.STRING, + user); + } + } + + protected void setPropertyIfAbsent( org.modeshape.graph.session.GraphSession.Node node, + Name primaryTypeName, + List mixinTypeNames, + boolean skipProtected, + Name propertyName, + int propertyType, + Object value ) { + if (node.getProperty(propertyName) != null) return; + Property graphProp = propertyFactory.create(propertyName, value); + JcrPropertyDefinition propDefn = findBestPropertyDefintion(primaryTypeName, + mixinTypeNames, + graphProp, + propertyType, + true, + skipProtected); + AbstractJcrNode jcrNode = node.getPayload().getJcrNode(); + AbstractJcrProperty jcrProp = new JcrSingleValueProperty(SessionCache.this, jcrNode, propertyName); + JcrPropertyPayload propPayload = new JcrPropertyPayload(propDefn.getId(), propertyType, jcrProp); + node.setProperty(graphProp, false, propPayload); } @Override Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (revision 1905) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (working copy) @@ -243,8 +243,8 @@ public class JcrTckTest { TestSuite suite = new TestSuite("JCR 2.0 API tests"); suite.addTest(levelOneSuite()); - // suite.addTest(levelTwoSuite()); - // suite.addTest(new OptionalFeatureTests()); + suite.addTest(levelTwoSuite()); + suite.addTest(new OptionalFeatureTests()); return suite; }