Index: modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (revision 1760) +++ modeshape-graph/src/main/java/org/modeshape/graph/GraphI18n.java (working copy) @@ -137,6 +137,7 @@ public final class GraphI18n { public static I18n nodeHasAlreadyBeenRemovedFromThisSession; public static I18n unableToMoveNodeToBeChildOfDecendent; public static I18n childNotFound; + public static I18n unableToRefreshPropertiesBecauseNodeIsModified; /* Query */ public static I18n unknownQueryLanguage; Index: modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (revision 1760) +++ modeshape-graph/src/main/java/org/modeshape/graph/session/GraphSession.java (working copy) @@ -801,6 +801,34 @@ public class GraphSession { } /** + * Refreshes the properties for the given node only. + *

+ * This method is not recursive and will not modify or access any descendants of the given node. + *

+ *

+ * NOTE: Calling this method on a node that already has modified properties can result in the enqueued property changes + * overwriting the current properties on a save() call. This method should be used with great care to avoid this + * situation. + *

+ * + * @param node the node for which the properties are to be refreshed; may not be null + * @throws InvalidStateException if the node is new + * @throws RepositorySourceException if any error resulting while reading information from the repository + */ + public void refreshProperties( Node node ) throws InvalidStateException, RepositorySourceException { + assert node != null; + + if (node.isNew()) { + I18n msg = GraphI18n.unableToRefreshPropertiesBecauseNodeIsModified; + String path = node.getPath().getString(context.getNamespaceRegistry()); + throw new InvalidStateException(msg.text(path, workspaceName)); + } + + org.modeshape.graph.Node persistentNode = store.getNodeAt(node.getLocation()); + nodeOperations.materializeProperties(persistentNode, node); + } + + /** * Save any changes that have been accumulated by this session. * * @throws PathNotFoundException if the state of this session is invalid and is attempting to change a node that doesn't exist @@ -1025,7 +1053,7 @@ public class GraphSession { public static interface Operations { /** - * Update the node with the information from the persistent store. + * Update the children and properties for the node with the information from the persistent store. * * @param persistentNode the persistent node that should be converted into a node info; never null * @param node the session's node representation that is to be updated; never null @@ -1034,6 +1062,15 @@ public class GraphSession { Node node ); /** + * Update the properties ONLY for the node with the information from the persistent store. + * + * @param persistentNode the persistent node that should be converted into a node info; never null + * @param node the session's node representation that is to be updated; never null + */ + void materializeProperties( org.modeshape.graph.Node persistentNode, + Node node ); + + /** * Signal that the node's {@link GraphSession.Node#getLocation() location} has been changed * * @param node the node with the new location @@ -1242,6 +1279,25 @@ public class GraphSession { /** * {@inheritDoc} * + * @see GraphSession.Operations#materializeProperties(org.modeshape.graph.Node, GraphSession.Node) + */ + public void materializeProperties( org.modeshape.graph.Node persistentNode, + Node node ) { + // Create the map of property info objects ... + Map> properties = new HashMap>(); + for (Property property : persistentNode.getProperties()) { + Name propertyName = property.getName(); + PropertyInfo info = new PropertyInfo(property, property.isMultiple(), + Status.UNCHANGED, null); + properties.put(propertyName, info); + } + // Set only the children ... + node.loadedWith(properties); + } + + /** + * {@inheritDoc} + * * @see GraphSession.Operations#postUpdateLocation(GraphSession.Node, org.modeshape.graph.Location) */ public void postUpdateLocation( Node node, @@ -1694,17 +1750,27 @@ public class GraphSession { child.updateLocation(segment); } } + + loadedWith(properties); + + // Set the expiration time ... + this.expirationTime = expirationTime != null ? expirationTime.getMilliseconds() : Long.MAX_VALUE; + } + + /** + * Define the persistent property information that this node is to be populated with. This method does not cause the + * node's information to be read from the store. + * + * @param properties the properties for this node; may not be null + */ + public void loadedWith( Map> properties ) { // Load the properties ... if (properties.isEmpty()) { this.properties = cache.NO_PROPERTIES; } else { this.properties = new HashMap>(properties); } - - // Set the expiration time ... - this.expirationTime = expirationTime != null ? expirationTime.getMilliseconds() : Long.MAX_VALUE; } - /** * Reconstruct the location object for this node, given the information at the parent. * Index: modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties =================================================================== --- modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (revision 1760) +++ modeshape-graph/src/main/resources/org/modeshape/graph/GraphI18n.properties (working copy) @@ -125,6 +125,7 @@ unableToSaveNodeThatWasCreatedSincePreviousSave = Unable to save node "{0}" in w nodeHasAlreadyBeenRemovedFromThisSession = Node "{0}" in workspace "{1} has already been removed from this session unableToMoveNodeToBeChildOfDecendent = Node "{0}" in workspace "{2}" cannot be moved under a decendant node ("{1}") childNotFound = Child "{0}" could not be found under "{1}" in workspace "{2}" +unableToRefreshPropertiesBecauseNodeIsModified = Unabled to refresh "{0}" in workspace "{1}" because this node is new or modified # Query unknownQueryLanguage = '{0}' is not a known query language Index: modeshape-jcr/src/main/java/org/modeshape/jcr/DefinitionCache.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/DefinitionCache.java (revision 1760) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/DefinitionCache.java (working copy) @@ -128,9 +128,13 @@ final class DefinitionCache { /* * If the child node was already defined in the type hierarchy at some other level, ignore the definition * - it was overridden by the previous definition. This relies on the fact that TypeA.getTypeAndSupertypes() - * always returns TypeX before TypeY if TypeX is closer to TypeA on the inheritance graph than TypeY is. + * always returns TypeX before TypeY if TypeX is closer to TypeA on the inheritance graph than TypeY is... + * + * ...UNLESS this is a residual definition, in which case side-by-side definitions must be allowed per 6.7.8 + * of the 1.0.1 specification. */ - if (allChildNodeDefinitions.containsKey(name) && !namesFromThisType.contains(name)) { + if (allChildNodeDefinitions.containsKey(name) && !namesFromThisType.contains(name) + && !JcrNodeType.RESIDUAL_NAME.equals(name)) { continue; } @@ -150,9 +154,13 @@ final class DefinitionCache { /* * If the property was already defined in the type hierarchy at some other level, ignore the definition * - it was overridden by the previous definition. This relies on the fact that TypeA.getTypeAndSupertypes() - * always returns TypeX before TypeY if TypeX is closer to TypeA on the inheritance graph than TypeY is. + * always returns TypeX before TypeY if TypeX is closer to TypeA on the inheritance graph than TypeY is... + * + * ...UNLESS this is a residual definition, in which case side-by-side definitions must be allowed per 6.7.8 + * of the 1.0.1 specification. */ - if (allPropertyDefinitions.containsKey(name) && !namesFromThisType.contains(name)) { + if (allPropertyDefinitions.containsKey(name) && !namesFromThisType.contains(name) + && !JcrNodeType.RESIDUAL_NAME.equals(name)) { continue; } if (definition.isMultiple()) { Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionManager.java (revision 1760) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrVersionManager.java (working copy) @@ -466,9 +466,11 @@ final class JcrVersionManager { org.modeshape.graph.property.Property predecessors = propFactory.create(JcrLexicon.PREDECESSORS, newPreds); Graph graph = workspace().graph(); - graph.set(isCheckedOut, predecessors, multiValuedProps).on(node.uuid()).and(); + Location location = Location.create(node.uuid()); + graph.set(isCheckedOut, predecessors, multiValuedProps).on(location).and(); - node.refresh(true); + cache().refreshProperties(location); + // node.refresh(true); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (revision 1760) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/SessionCache.java (working copy) @@ -305,6 +305,28 @@ class SessionCache { } /** + * Refreshes the properties for the node with the given UUID. + * + * @param location the location of the node that is to be refreshed; may not be null + * @throws InvalidItemStateException if the node being refreshed no longer exists + * @throws RepositoryException if any error resulting while saving the changes to the repository + */ + public void refreshProperties( Location location ) throws InvalidItemStateException, RepositoryException { + assert location != null; + try { + Node node = graphSession.findNodeWith(location); + + graphSession.refreshProperties(node); + } catch (InvalidStateException e) { + throw new InvalidItemStateException(e.getLocalizedMessage()); + } catch (org.modeshape.graph.property.PathNotFoundException e) { + throw new InvalidItemStateException(e.getLocalizedMessage()); + } catch (RepositorySourceException e) { + throw new RepositoryException(e.getLocalizedMessage()); + } + } + + /** * Find the best definition for the child node with the given name on the node with the given UUID. * * @param parent the parent node; may not be null @@ -2172,127 +2194,32 @@ class SessionCache { final class JcrNodeOperations extends GraphSession.NodeOperations { private final Logger LOGGER = Logger.getLogger(JcrNodeOperations.class); - /** - * {@inheritDoc} - * - * @see org.modeshape.graph.session.GraphSession.Operations#materialize(org.modeshape.graph.Node, - * org.modeshape.graph.session.GraphSession.Node) - */ - @Override - public void materialize( org.modeshape.graph.Node persistentNode, - Node node ) { - // Now get the ModeShape node's UUID and find the ModeShape property containing the UUID ... - Location location = node.getLocation(); - UUID uuid = location.getUuid(); - org.modeshape.graph.property.Property uuidProperty = null; - if (uuid != null) { - // Check for an identification property ... - uuidProperty = location.getIdProperty(JcrLexicon.UUID); - if (uuidProperty == null) { - uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid); - } - } - if (uuid != null && uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid); + + private Map> buildProperties( org.modeshape.graph.Node persistentNode, + Node node, + JcrNodePayload nodePayload, + boolean referenceable + ) { + + AbstractJcrNode jcrNode = nodePayload.getJcrNode(); + Name primaryTypeName = nodePayload.getPrimaryTypeName(); + List mixinTypeNames = nodePayload.getMixinTypeNames(); - // Look for the primary type of the node ... + Location location = persistentNode.getLocation(); Map graphProperties = persistentNode.getPropertiesByName(); - final boolean isRoot = location.getPath().isRoot(); - Name primaryTypeName = null; - org.modeshape.graph.property.Property primaryTypeProperty = graphProperties.get(JcrLexicon.PRIMARY_TYPE); - if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) { - try { - primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue()); - } catch (ValueFormatException e) { - // use the default ... - } - } - if (primaryTypeName == null) { + + if (!graphProperties.containsKey(JcrLexicon.PRIMARY_TYPE)) { + Property primaryTypeProperty; // We have to have a primary type, so use the default ... - if (isRoot) { - primaryTypeName = ModeShapeLexicon.ROOT; + if (location.getPath().isRoot()) { primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName); } else { - primaryTypeName = defaultPrimaryTypeName; primaryTypeProperty = defaultPrimaryTypeProperty; } // We have to add this property to the graph node... graphProperties = new HashMap(graphProperties); graphProperties.put(primaryTypeProperty.getName(), primaryTypeProperty); } - assert primaryTypeProperty != null; - assert primaryTypeProperty.isEmpty() == false; - - // Look for a node definition stored on the node ... - JcrNodeDefinition definition = null; - org.modeshape.graph.property.Property nodeDefnProperty = graphProperties.get(ModeShapeIntLexicon.NODE_DEFINITON); - if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) { - String nodeDefinitionString = stringFactory.create(nodeDefnProperty.getFirstValue()); - NodeDefinitionId id = NodeDefinitionId.fromString(nodeDefinitionString, nameFactory); - definition = nodeTypes().getNodeDefinition(id); - } - // Figure out the node definition for this node ... - if (definition == null) { - if (isRoot) { - try { - definition = nodeTypes().getRootNodeDefinition(); - } catch (RepositoryException e) { - // Shouldn't really happen ... - throw new ValidationException(e.getMessage(), e); - } - } else { - Name childName = node.getName(); - Node parent = node.getParent(); - JcrNodePayload parentInfo = parent.getPayload(); - int numExistingChildrenWithSameName = parent.getChildrenCount(childName); - // The children include this node, so we need to subtract one from the count so that the - // number of existing children is either 0 (if there are no other SNS nodes) or 1+ - // (if there are at least 2 SNS nodes) - --numExistingChildrenWithSameName; - definition = nodeTypes().findChildNodeDefinition(parentInfo.getPrimaryTypeName(), - parentInfo.getMixinTypeNames(), - childName, - primaryTypeName, - numExistingChildrenWithSameName, - false); - } - } - if (definition == null) { - String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(readable(node.getPath()), - workspaceName(), - sourceName()); - throw new ValidationException(msg); - } - - // ------------------------------------------------------ - // Set the node's properties ... - // ------------------------------------------------------ - boolean referenceable = false; - - // Start with the primary type ... - JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName); - if (primaryType == null) { - Path path = location.getPath(); - String msg = JcrI18n.missingNodeTypeForExistingNode.text(readable(primaryTypeName), - readable(path), - workspaceName(), - sourceName()); - throw new ValidationException(msg); - } - if (primaryType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true; - - // The process the mixin types ... - Property mixinTypesProperty = graphProperties.get(JcrLexicon.MIXIN_TYPES); - List mixinTypeNames = null; - if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) { - for (Object mixinTypeValue : mixinTypesProperty) { - Name mixinTypeName = nameFactory.create(mixinTypeValue); - if (mixinTypeNames == null) mixinTypeNames = new LinkedList(); - mixinTypeNames.add(mixinTypeName); - JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName); - if (mixinType == null) continue; - if (!referenceable && mixinType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true; - } - } // Create the set of multi-valued property names ... Set multiValuedPropertyNames = EMPTY_NAMES; @@ -2302,10 +2229,16 @@ class SessionCache { multiValuedPropertyNames = getSingleMultiPropertyNames(multiValuedPropNamesProp, location); } - // Create the JCR Node payload object ... - JcrNodePayload nodePayload = new JcrNodePayload(SessionCache.this, node, primaryTypeName, mixinTypeNames, - definition.getId()); - AbstractJcrNode jcrNode = nodePayload.getJcrNode(); + UUID uuid = location.getUuid(); + org.modeshape.graph.property.Property uuidProperty = null; + if (uuid != null) { + // Check for an identification property ... + uuidProperty = location.getIdProperty(JcrLexicon.UUID); + if (uuidProperty == null) { + uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid); + } + } + if (uuid != null && uuidProperty == null) uuidProperty = propertyFactory.create(JcrLexicon.UUID, uuid); // Now create the JCR property object wrappers around the other properties ... Map> props = new HashMap>(); @@ -2383,6 +2316,7 @@ class SessionCache { // Now add the "jcr:uuid" property if and only if referenceable ... if (referenceable) { + // We know that this property is single-valued JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, uuid); JcrPropertyDefinition propDefn = nodeTypes().findPropertyDefinition(primaryTypeName, @@ -2412,6 +2346,148 @@ class SessionCache { props.put(info.getName(), info); } + return props; + } + + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.session.GraphSession.Operations#materializeProperties(org.modeshape.graph.Node, + * org.modeshape.graph.session.GraphSession.Node) + */ + @Override + public void materializeProperties( org.modeshape.graph.Node persistentNode, + Node node ) { + + JcrNodePayload nodePayload = node.getPayload(); + boolean referenceable = false; + + try { + referenceable = isReferenceable(node); + } catch (RepositoryException re) { + throw new IllegalStateException(re); + } + + Map> props = buildProperties(persistentNode, node, nodePayload, referenceable); + // Set the information on the node ... + node.loadedWith(props); + + } + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.session.GraphSession.Operations#materialize(org.modeshape.graph.Node, + * org.modeshape.graph.session.GraphSession.Node) + */ + @Override + public void materialize( org.modeshape.graph.Node persistentNode, + Node node ) { + // Now get the ModeShape node's UUID and find the ModeShape property containing the UUID ... + Location location = node.getLocation(); + + // Look for the primary type of the node ... + Map graphProperties = persistentNode.getPropertiesByName(); + final boolean isRoot = location.getPath().isRoot(); + Name primaryTypeName = null; + org.modeshape.graph.property.Property primaryTypeProperty = graphProperties.get(JcrLexicon.PRIMARY_TYPE); + if (primaryTypeProperty != null && !primaryTypeProperty.isEmpty()) { + try { + primaryTypeName = factories.getNameFactory().create(primaryTypeProperty.getFirstValue()); + } catch (ValueFormatException e) { + // use the default ... + } + } + if (primaryTypeName == null) { + // We have to have a primary type, so use the default ... + if (isRoot) { + primaryTypeName = ModeShapeLexicon.ROOT; + primaryTypeProperty = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, primaryTypeName); + } else { + primaryTypeName = defaultPrimaryTypeName; + primaryTypeProperty = defaultPrimaryTypeProperty; + } + } + assert primaryTypeProperty != null; + assert primaryTypeProperty.isEmpty() == false; + + // Look for a node definition stored on the node ... + JcrNodeDefinition definition = null; + org.modeshape.graph.property.Property nodeDefnProperty = graphProperties.get(ModeShapeIntLexicon.NODE_DEFINITON); + if (nodeDefnProperty != null && !nodeDefnProperty.isEmpty()) { + String nodeDefinitionString = stringFactory.create(nodeDefnProperty.getFirstValue()); + NodeDefinitionId id = NodeDefinitionId.fromString(nodeDefinitionString, nameFactory); + definition = nodeTypes().getNodeDefinition(id); + } + // Figure out the node definition for this node ... + if (definition == null) { + if (isRoot) { + try { + definition = nodeTypes().getRootNodeDefinition(); + } catch (RepositoryException e) { + // Shouldn't really happen ... + throw new ValidationException(e.getMessage(), e); + } + } else { + Name childName = node.getName(); + Node parent = node.getParent(); + JcrNodePayload parentInfo = parent.getPayload(); + int numExistingChildrenWithSameName = parent.getChildrenCount(childName); + // The children include this node, so we need to subtract one from the count so that the + // number of existing children is either 0 (if there are no other SNS nodes) or 1+ + // (if there are at least 2 SNS nodes) + --numExistingChildrenWithSameName; + definition = nodeTypes().findChildNodeDefinition(parentInfo.getPrimaryTypeName(), + parentInfo.getMixinTypeNames(), + childName, + primaryTypeName, + numExistingChildrenWithSameName, + false); + } + } + if (definition == null) { + String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(readable(node.getPath()), + workspaceName(), + sourceName()); + throw new ValidationException(msg); + } + + // ------------------------------------------------------ + // Set the node's properties ... + // ------------------------------------------------------ + boolean referenceable = false; + + // Start with the primary type ... + JcrNodeType primaryType = nodeTypes().getNodeType(primaryTypeName); + if (primaryType == null) { + Path path = location.getPath(); + String msg = JcrI18n.missingNodeTypeForExistingNode.text(readable(primaryTypeName), + readable(path), + workspaceName(), + sourceName()); + throw new ValidationException(msg); + } + if (primaryType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true; + + // The process the mixin types ... + Property mixinTypesProperty = graphProperties.get(JcrLexicon.MIXIN_TYPES); + List mixinTypeNames = null; + if (mixinTypesProperty != null && !mixinTypesProperty.isEmpty()) { + for (Object mixinTypeValue : mixinTypesProperty) { + Name mixinTypeName = nameFactory.create(mixinTypeValue); + if (mixinTypeNames == null) mixinTypeNames = new LinkedList(); + mixinTypeNames.add(mixinTypeName); + JcrNodeType mixinType = nodeTypes().getNodeType(mixinTypeName); + if (mixinType == null) continue; + if (!referenceable && mixinType.isNodeType(JcrMixLexicon.REFERENCEABLE)) referenceable = true; + } + } + + // Create the JCR Node payload object ... + JcrNodePayload nodePayload = new JcrNodePayload(SessionCache.this, node, primaryTypeName, mixinTypeNames, + definition.getId()); + + Map> props = buildProperties(persistentNode, node, nodePayload, referenceable); + // Set the information on the node ... node.loadedWith(persistentNode.getChildren(), props, persistentNode.getExpirationTime()); node.setPayload(nodePayload); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (revision 1760) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/ModeShapeTckTest.java (working copy) @@ -854,4 +854,23 @@ public class ModeShapeTckTest extends AbstractJCRTest { session.save(); } + + public void testShouldAllowCheckoutAfterMove() throws Exception { + // q.v., MODE-??? + + session = helper.getReadWriteSession(); + + Node root = session.getRootNode(); + Node sourceNode = root.addNode("versionableSource", "nt:unstructured"); + sourceNode.addMixin("mix:versionable"); + + Node targetNode = root.addNode("versionableTarget", "nt:unstructured"); + session.save(); + + String sourceName = sourceNode.getName(); + session.move(sourceNode.getPath(), targetNode.getPath() + "/" + sourceName); + sourceNode = targetNode.getNode(sourceName); + sourceNode.checkout(); + + } }