Index: dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java (revision 839) +++ dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java (working copy) @@ -193,12 +193,12 @@ if (node == null) return; // Look up the new parent, which must exist ... Path newPath = request.into().getPath(); - Path newParentPath = newPath.getParent(); - InMemoryNode newParent = workspace.getNode(newParentPath); - node.setParent(newParent); - newPath = getExecutionContext().getValueFactories().getPathFactory().create(newParentPath, node.getName()); + + InMemoryNode newParent = workspace.getNode(newPath); + workspace.moveNode(getExecutionContext(), node, workspace, newParent); + Location oldLocation = getActualLocation(request.from().getPath(), node); - Location newLocation = Location.create(newPath, node.getUuid()); + Location newLocation = Location.create(newPath, newParent.getUuid()); request.setActualLocations(oldLocation, newLocation); } Index: dna-graph/src/main/java/org/jboss/dna/graph/property/basic/ChildPath.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/property/basic/ChildPath.java (revision 839) +++ dna-graph/src/main/java/org/jboss/dna/graph/property/basic/ChildPath.java (working copy) @@ -109,7 +109,7 @@ @Override public Segment getSegment( int index ) { if (index == (size - 1)) return child; - return parent.getSegment(index - 1); + return parent.getSegment(index); } /** Index: dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java (revision 839) +++ dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java (working copy) @@ -211,7 +211,9 @@ */ public boolean changes( String workspace, Path path ) { - return this.workspaceName.equals(workspace) && into.hasPath() && into.getPath().isAtOrBelow(path); + return this.workspaceName.equals(workspace) + && (into.hasPath() && into.getPath().isAtOrBelow(path) + || from.hasPath() && from.getPath().isAtOrBelow(path)); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java (revision 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/cache/ChangedNodeInfo.java (working copy) @@ -86,6 +86,8 @@ */ private NodeDefinitionId changedDefinitionId; + private List peers; + /** * Create an immutable NodeInfo instance. * @@ -97,6 +99,35 @@ } /** + * Returns the peer nodes for this changed node. + *

+ * Peer nodes are nodes that must be saved with this node (e.g., the other changed node in a + * {@link javax.jcr.Session#move(String, String)} operation. + *

+ * + * @return a collection of the UUIDs for any other nodes that must be saved with this node; may be null + */ + public final Collection getPeers() { + return peers; + } + + /** + * Adds a peer node to this change. + *

+ * Peer nodes are nodes that must be saved with this node (e.g., the other changed node in a + * {@link javax.jcr.Session#move(String, String)} operation. + *

+ * + * @param peerUuid the UUID of the peer node + */ + public void addPeer( UUID peerUuid ) { + if (peers == null) { + peers = new LinkedList(); + } + peers.add(peerUuid); + } + + /** * Return the original node information. May be null if this is a new node. * * @return the original node information Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (revision 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java (working copy) @@ -62,6 +62,7 @@ import org.jboss.dna.graph.property.Name; import org.jboss.dna.graph.property.NamespaceRegistry; import org.jboss.dna.graph.property.Path; +import org.jboss.dna.graph.property.PathFactory; import org.jboss.dna.graph.property.ValueFactories; import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry; import org.jboss.dna.jcr.JcrContentHandler.EnclosingSAXException; @@ -420,7 +425,7 @@ * @throws PathNotFoundException if the path could not be found * @throws RepositoryException if there is a problem */ - Node getNode( Path path ) throws RepositoryException, PathNotFoundException { + AbstractJcrNode getNode( Path path ) throws RepositoryException, PathNotFoundException { if (path.isRoot()) return cache.findJcrRootNode(); return cache.findJcrNode(null, path.relativeTo(rootPath)); } @@ -678,12 +683,30 @@ /** * {@inheritDoc} * - * @throws UnsupportedOperationException always * @see javax.jcr.Session#move(java.lang.String, java.lang.String) */ public void move( String srcAbsPath, - String destAbsPath ) { - throw new UnsupportedOperationException(); + String destAbsPath ) throws ItemExistsException, RepositoryException { + CheckArg.isNotNull(srcAbsPath, "srcAbsPath"); + CheckArg.isNotNull(destAbsPath, "destAbsPath"); + + PathFactory pathFactory = executionContext.getValueFactories().getPathFactory(); + Path destPath = pathFactory.create(destAbsPath); + + Path.Segment newNodeName = destPath.getSegment(destPath.size() - 1); + // Doing a literal test here because the path factory will canonicalize "/node[1]" to "/node" + if (destAbsPath.endsWith("]")) { + throw new RepositoryException(); + } + + AbstractJcrNode sourceNode = getNode(pathFactory.create(srcAbsPath)); + AbstractJcrNode newParentNode = getNode(destPath.getParent()); + + if (newParentNode.hasNode(newNodeName.getString(executionContext.getNamespaceRegistry()))) { + throw new ItemExistsException(); + } + + newParentNode.editor().moveToBeChild(sourceNode.nodeUuid, newNodeName.getName()); } /** Index: dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (revision 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/PropertyDefinitionId.java (working copy) @@ -74,7 +74,7 @@ private final String stringRepresentation; /** - * Create a new identifier for a propety definition. + * Create a new identifier for a property definition. * * @param nodeTypeName the name of the node type; may not be null * @param propertyDefinitionName the name of the property definition, which may be a {@link #ANY_NAME residual property}; may Index: dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (revision 839) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/SessionCache.java (working copy) @@ -445,6 +448,7 @@ * newly created node will have it's parent's UUID in branchUuids, but not the new node's uuid. */ Set uuidsUnderBranch = new HashSet(); + LinkedList peersToCheck = new LinkedList(); for (UUID branchUuid : branchUuids) { uuidsUnderBranch.add(branchUuid); ChangedNodeInfo changedNode = changedNodes.get(branchUuid); @@ -452,10 +456,22 @@ for (ChildNode childNode : changedNode.getChildren()) { uuidsUnderBranch.add(childNode.getUuid()); } + + Collection peers = changedNode.getPeers(); + if (peers != null) peersToCheck.addAll(peers); } } + /* + * Need to check that any peers in a Session.move operation are both in the save + */ + for (UUID peerUuid : peersToCheck) { + if (!uuidsUnderBranch.contains(peerUuid)) { + throw new ConstraintViolationException(); + } + } + // Now execute the branch ... Graph.Batch branchBatch = store.batch(new BatchRequestBuilder(branchRequests)); try { @@ -899,6 +1030,7 @@ * list of children. This method automatically disconnects the node from its current parent. * * @param nodeUuid the UUID of the existing node; may not be null + * @param newNodeName * @return the representation of the newly-added child, which includes the {@link ChildNode#getSnsIndex() * same-name-sibling index} * @throws ItemNotFoundException if the specified child node could be found in the session or workspace @@ -906,7 +1038,7 @@ * @throws ConstraintViolationException if moving the node into this node violates this node's definition * @throws RepositoryException if any other error occurs while reading information from the repository */ - public ChildNode moveToBeChild( UUID nodeUuid ) + public ChildNode moveToBeChild( UUID nodeUuid, Name newNodeName ) throws ItemNotFoundException, InvalidItemStateException, ConstraintViolationException, RepositoryException { if (nodeUuid.equals(node.getUuid()) || isAncestor(nodeUuid)) { @@ -942,8 +1074,8 @@ if (!definition.getId().equals(node.getDefinitionId())) { // The node definition changed, so try to set the property ... try { - JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, definition.getId() - .getString()); + JcrValue value = new JcrValue(factories(), SessionCache.this, PropertyType.STRING, + definition.getId().getString()); setProperty(DnaLexicon.NODE_DEFINITON, value); } catch (ConstraintViolationException e) { // We can't set this property on the node (according to the node definition). @@ -959,17 +1091,71 @@ // Remove the node from the current parent and add it to this ... child = existingParentInfo.removeChild(nodeUuid, pathFactory); - ChildNode newChild = node.addChild(child.getName(), child.getUuid(), pathFactory); + ChildNode newChild = node.addChild(newNodeName, child.getUuid(), pathFactory); // Set the child's changed representation to point to this node as its parent ... existingNodeInfo.setParent(node.getUuid()); - + + // Set up the peer relationship between the two nodes that must be saved together + node.addPeer(existingParent); + existingParentInfo.addPeer(node.getUuid()); + // Now, record the operation to do this ... operations.move(existingNodeEditor.currentLocation).into(currentLocation); - + return newChild; }