Index: dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/ForkRequestProcessor.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/ForkRequestProcessor.java (revision 1049) +++ dna-graph/src/main/java/org/jboss/dna/graph/connector/federation/ForkRequestProcessor.java (working copy) @@ -1262,7 +1262,8 @@ // Create the pushed-down request ... CopyBranchRequest pushDown = new CopyBranchRequest(fromProxy.location(), fromProxy.workspaceName(), intoProxy.location(), intoProxy.workspaceName(), request.desiredName(), - request.conflictBehavior()); + request.nodeConflictBehavior(), + request.uuidConflictBehavior()); // Create the federated request ... FederatedRequest federatedRequest = new FederatedRequest(request); federatedRequest.add(pushDown, sameLocation, false, fromProxy.projection(), intoProxy.projection()); Index: dna-graph/src/main/java/org/jboss/dna/graph/Graph.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/Graph.java (revision 1049) +++ dna-graph/src/main/java/org/jboss/dna/graph/Graph.java (working copy) @@ -78,6 +78,7 @@ import org.jboss.dna.graph.request.Request; import org.jboss.dna.graph.request.RequestBuilder; import org.jboss.dna.graph.request.UnsupportedRequestException; +import org.jboss.dna.graph.request.UuidConflictBehavior; import org.jboss.dna.graph.request.VerifyWorkspaceRequest; import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior; import org.jboss.dna.graph.request.CreateWorkspaceRequest.CreateConflictBehavior; @@ -564,17 +565,19 @@ public Copy copy( Location from ) { return new CopyAction(this, from) { @Override - protected Graph submit( Locations from, + protected Graph submit( String fromWorkspaceName, + Locations from, Location into, Name childName ) { - String workspaceName = getCurrentWorkspaceName(); + String workspaceName = fromWorkspaceName != null ? fromWorkspaceName : getCurrentWorkspaceName(); do { requests.copyBranch(from.getLocation(), workspaceName, into, - workspaceName, + getCurrentWorkspaceName(), childName, - NodeConflictBehavior.APPEND); + NodeConflictBehavior.APPEND, + UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID); } while ((from = from.next()) != null); return and(); } @@ -1734,15 +1737,13 @@ } public List under( Location at ) { - return requests.readBlockOfChildren(at, getCurrentWorkspaceName(), startingIndex, blockSize) - .getChildren(); + return requests.readBlockOfChildren(at, getCurrentWorkspaceName(), startingIndex, blockSize).getChildren(); } }; } public List startingAfter( final Location previousSibling ) { - return requests.readNextBlockOfChildren(previousSibling, getCurrentWorkspaceName(), blockSize) - .getChildren(); + return requests.readNextBlockOfChildren(previousSibling, getCurrentWorkspaceName(), blockSize).getChildren(); } public List startingAfter( String pathOfPreviousSibling ) { @@ -2368,12 +2369,19 @@ assertNotExecuted(); return new CopyAction(this.nextRequests, from) { @Override - protected BatchConjunction submit( Locations from, + protected BatchConjunction submit( String fromWorkspaceName, + Locations from, Location into, Name copyName ) { - String workspaceName = getCurrentWorkspaceName(); + String workspaceName = fromWorkspaceName != null ? fromWorkspaceName : getCurrentWorkspaceName(); do { - requestQueue.copyBranch(from.getLocation(), workspaceName, into, workspaceName, copyName); + requestQueue.copyBranch(from.getLocation(), + workspaceName, + into, + workspaceName, + copyName, + null, + UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID); } while ((from = from.next()) != null); return and(); } @@ -3993,10 +4001,36 @@ * @param The interface that is to be returned when this request is completed * @author Randall Hauch */ - public interface Copy extends To, Into, And> { + public interface Copy + extends WithUuids>>, FromWorkspace>, CopyTarget, And> { } + public interface CopyTarget extends To, Into { + } + /** + * The interface for specifying that a node should come from a workspace other than the current workspace. + * + * @param The interface that is to be returned when this request is completed + */ + public interface FromWorkspace { + Next fromWorkspace( String workspaceName ); + } + + /** + * The interface for specifying how UUID conflicts should be handled. + * + * @param The interface that is to be returned when this request is completed + */ + public interface WithUuids { + Next removingMatchingUuids(); + + Next failingIfUuidsMatch(); + + Next unlessMatchingUuidExists(); + } + + /** * The interface for defining additional properties on a new node. * * @param The interface that is to be returned when this create request is completed @@ -5473,10 +5507,7 @@ } public SubgraphNode getNode( Name relativePath ) { - Path path = getGraph().getContext() - .getValueFactories() - .getPathFactory() - .create(getLocation().getPath(), relativePath); + Path path = getGraph().getContext().getValueFactories().getPathFactory().create(getLocation().getPath(), relativePath); path = path.getNormalizedPath(); return getNode(path); } @@ -5762,12 +5793,16 @@ @NotThreadSafe protected abstract class CopyAction extends AbstractAction implements Copy { - private final Locations from; + protected Locations from; + protected String fromWorkspaceName; + protected UuidConflictBehavior uuidConflictBehavior; /*package*/CopyAction( T afterConjunction, Location from ) { super(afterConjunction); this.from = new Locations(from); + this.fromWorkspaceName = Graph.this.getCurrentWorkspaceName(); + this.uuidConflictBehavior = UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID; } public Copy and( Location from ) { @@ -5809,38 +5844,61 @@ /** * Submit any requests to move the targets into the supplied parent location * + * @param fromWorkspaceName the name of the workspace containing the {@code from} locations * @param from the locations that are being copied * @param into the parent location * @param nameForCopy the name that should be used for the copy, or null if the name should be the same as the original * @return this object, for method chaining */ - protected abstract T submit( Locations from, - Location into, - Name nameForCopy ); + protected abstract T submit( String fromWorkspaceName, + Locations from, + Location into, + Name nameForCopy ); + public FromWorkspace> failingIfUuidsMatch() { + this.uuidConflictBehavior = UuidConflictBehavior.THROW_EXCEPTION; + return this; + } + + public FromWorkspace> removingMatchingUuids() { + this.uuidConflictBehavior = UuidConflictBehavior.REPLACE_EXISTING_NODE; + return this; + } + + public FromWorkspace> unlessMatchingUuidExists() { + this.uuidConflictBehavior = UuidConflictBehavior.DO_NOT_COPY; + return this; + } + + public CopyTarget fromWorkspace( String workspaceName ) { + this.fromWorkspaceName = workspaceName; + + return this; + } + public T into( Location into ) { - return submit(from, into, null); + return submit(this.fromWorkspaceName, this.from, into, null); } public T into( Path into ) { - return submit(from, Location.create(into), null); + return submit(this.fromWorkspaceName, this.from, Location.create(into), null); } public T into( UUID into ) { - return submit(from, Location.create(into), null); + return submit(this.fromWorkspaceName, this.from, Location.create(into), null); } public T into( Property firstIdProperty, Property... additionalIdProperties ) { - return submit(from, Location.create(firstIdProperty, additionalIdProperties), null); + return submit(this.fromWorkspaceName, this.from, Location.create(firstIdProperty, additionalIdProperties), null); } public T into( Property into ) { - return submit(from, Location.create(into), null); + return submit(this.fromWorkspaceName, this.from, Location.create(into), null); } public T into( String into ) { - return submit(from, Location.create(createPath(into)), null); + return submit(this.fromWorkspaceName, this.from, Location.create(createPath(into)), null); } public T to( Location desiredLocation ) { @@ -5852,7 +5910,7 @@ throw new IllegalArgumentException(GraphI18n.unableToCopyToTheRoot.text(this.from, desiredLocation)); } Path parent = desiredPath.getParent(); - return submit(from, Location.create(parent), desiredPath.getLastSegment().getName()); + return submit(this.fromWorkspaceName, this.from, Location.create(parent), desiredPath.getLastSegment().getName()); } public T to( Path desiredPath ) { @@ -5860,7 +5918,7 @@ throw new IllegalArgumentException(GraphI18n.unableToCopyToTheRoot.text(this.from, desiredPath)); } Path parent = desiredPath.getParent(); - return submit(from, Location.create(parent), desiredPath.getLastSegment().getName()); + return submit(this.fromWorkspaceName, this.from, Location.create(parent), desiredPath.getLastSegment().getName()); } public T to( String desiredPath ) { Index: dna-graph/src/main/java/org/jboss/dna/graph/request/BatchRequestBuilder.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/request/BatchRequestBuilder.java (revision 1049) +++ dna-graph/src/main/java/org/jboss/dna/graph/request/BatchRequestBuilder.java (working copy) @@ -572,7 +572,8 @@ String intoWorkspace, Name nameForCopy ) { return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, - CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); + CopyBranchRequest.DEFAULT_NODE_CONFLICT_BEHAVIOR, + UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID)); } /** @@ -585,7 +586,10 @@ * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be * used * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the into - * location, or null if the default conflict behavior should be used + * location, or null if the default node conflict behavior should be used + * @param uuidConflictBehavior the expected behavior if a node with the same UUID as any of the nodes in the branch under the + * {@code from} location in the {@code fromWorkspace} workspace already exists in the workspace, or null if the default + * UUID conflict behavior should be used * @return this builder for method chaining; never null * @throws IllegalArgumentException if either of the locations or workspace names are null */ @@ -594,9 +598,12 @@ Location into, String intoWorkspace, Name nameForCopy, - NodeConflictBehavior conflictBehavior ) { - if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR; - return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior)); + NodeConflictBehavior conflictBehavior, + UuidConflictBehavior uuidConflictBehavior) { + if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_NODE_CONFLICT_BEHAVIOR; + if (uuidConflictBehavior == null) uuidConflictBehavior = CopyBranchRequest.DEFAULT_UUID_CONFLICT_BEHAVIOR; + return add(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior, + uuidConflictBehavior)); } /** @@ -628,7 +635,8 @@ Location into, String workspaceName, Name newNameForNode ) { - return add(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); + return add(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode, + MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); } /** @@ -647,7 +655,8 @@ Location before, String workspaceName, Name newNameForNode ) { - return add(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); + return add(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode, + MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); } /** Index: dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java (revision 1049) +++ dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java (working copy) @@ -41,14 +41,16 @@ private static final long serialVersionUID = 1L; - public static final NodeConflictBehavior DEFAULT_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND; + public static final NodeConflictBehavior DEFAULT_NODE_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND; + public static final UuidConflictBehavior DEFAULT_UUID_CONFLICT_BEHAVIOR = UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID; private final Location from; private final Location into; private final String fromWorkspace; private final String intoWorkspace; private final Name desiredNameForCopy; - private final NodeConflictBehavior conflictBehavior; + private final NodeConflictBehavior nodeConflictBehavior; + private final UuidConflictBehavior uuidConflictBehavior; private Location actualFromLocation; private Location actualIntoLocation; @@ -66,7 +68,7 @@ String fromWorkspace, Location into, String intoWorkspace ) { - this(from, fromWorkspace, into, intoWorkspace, null, DEFAULT_CONFLICT_BEHAVIOR); + this(from, fromWorkspace, into, intoWorkspace, null, DEFAULT_NODE_CONFLICT_BEHAVIOR, DEFAULT_UUID_CONFLICT_BEHAVIOR); } /** @@ -85,7 +87,7 @@ Location into, String intoWorkspace, Name nameForCopy ) { - this(from, fromWorkspace, into, intoWorkspace, nameForCopy, DEFAULT_CONFLICT_BEHAVIOR); + this(from, fromWorkspace, into, intoWorkspace, nameForCopy, DEFAULT_NODE_CONFLICT_BEHAVIOR, DEFAULT_UUID_CONFLICT_BEHAVIOR); } /** @@ -97,8 +99,10 @@ * @param intoWorkspace the name of the workspace where the into node is to be copied * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be * used - * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the into + * @param nodeConflictBehavior the expected behavior if an equivalently-named child already exists at the into * location + * @param uuidConflictBehavior the expected behavior if a node with the same UUID as any of the nodes in the branch under the + * {@code from} location in the {@code fromWorkspace} workspace already exists in the workspace * @throws IllegalArgumentException if any of the parameters are null */ public CopyBranchRequest( Location from, @@ -106,18 +110,21 @@ Location into, String intoWorkspace, Name nameForCopy, - NodeConflictBehavior conflictBehavior ) { + NodeConflictBehavior nodeConflictBehavior, + UuidConflictBehavior uuidConflictBehavior ) { CheckArg.isNotNull(from, "from"); CheckArg.isNotNull(into, "into"); CheckArg.isNotNull(fromWorkspace, "fromWorkspace"); CheckArg.isNotNull(intoWorkspace, "intoWorkspace"); - CheckArg.isNotNull(conflictBehavior, "conflictBehavior"); + CheckArg.isNotNull(nodeConflictBehavior, "nodeConflictBehavior"); + CheckArg.isNotNull(uuidConflictBehavior, "uuidConflictBehavior"); this.from = from; this.into = into; this.fromWorkspace = fromWorkspace; this.intoWorkspace = intoWorkspace; this.desiredNameForCopy = nameForCopy; - this.conflictBehavior = conflictBehavior; + this.nodeConflictBehavior = nodeConflictBehavior; + this.uuidConflictBehavior = uuidConflictBehavior; } /** @@ -191,11 +198,20 @@ * * @return the behavior specification */ - public NodeConflictBehavior conflictBehavior() { - return conflictBehavior; + public NodeConflictBehavior nodeConflictBehavior() { + return nodeConflictBehavior; } /** + * Get the expected behavior when one of the nodes in the branch has the same UUID as an existing node in the workspace. + * + * @return the behavior specification + */ + public UuidConflictBehavior uuidConflictBehavior() { + return uuidConflictBehavior; + } + + /** * Sets the actual and complete location of the node being renamed and its new location. This method must be called when * processing the request, and the actual location must have a {@link Location#getPath() path}. * @@ -313,7 +329,7 @@ CopyBranchRequest that = (CopyBranchRequest)obj; if (!this.from().equals(that.from())) return false; if (!this.into().equals(that.into())) return false; - if (!this.conflictBehavior().equals(that.conflictBehavior())) return false; + if (!this.nodeConflictBehavior().equals(that.nodeConflictBehavior())) return false; if (!this.fromWorkspace.equals(that.fromWorkspace)) return false; if (!this.intoWorkspace.equals(that.intoWorkspace)) return false; return true; Index: dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java =================================================================== --- dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java (revision 1049) +++ dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java (working copy) @@ -432,6 +432,8 @@ * used * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the into * location, or null if the default conflict behavior should be used + * @param uuidConflictBehavior the expected behavior if a node with the same UUID as any of the nodes in the branch under the + * {@code from} location in the {@code fromWorkspace} workspace already exists in the workspace * @return the request; never null * @throws IllegalArgumentException if either of the locations or workspace names are null */ @@ -440,9 +442,11 @@ Location into, String intoWorkspace, Name nameForCopy, - NodeConflictBehavior conflictBehavior ) { - if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_CONFLICT_BEHAVIOR; - return process(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior)); + NodeConflictBehavior conflictBehavior, + UuidConflictBehavior uuidConflictBehavior) { + if (conflictBehavior == null) conflictBehavior = CopyBranchRequest.DEFAULT_NODE_CONFLICT_BEHAVIOR; + return process(new CopyBranchRequest(from, fromWorkspace, into, intoWorkspace, nameForCopy, conflictBehavior, + uuidConflictBehavior)); } /** @@ -474,9 +478,10 @@ Location into, String workspaceName, Name newNameForNode ) { - return process(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); + return process(new MoveBranchRequest(from, into, null, workspaceName, newNameForNode, + MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); } - + /** * Create a request to move a branch from one location into another before the given child node of the new location. * @@ -490,11 +495,13 @@ */ public MoveBranchRequest moveBranch( Location from, Location into, - Location before, + Location before, String workspaceName, Name newNameForNode ) { - return process(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode, MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); - } + return process(new MoveBranchRequest(from, into, before, workspaceName, newNameForNode, + MoveBranchRequest.DEFAULT_CONFLICT_BEHAVIOR)); + } + /** * Create a request to move a branch from one location into another. * Index: dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java =================================================================== --- dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java (revision 1049) +++ dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java (working copy) @@ -72,6 +72,7 @@ import org.jboss.dna.graph.request.Request; import org.jboss.dna.graph.request.SetPropertyRequest; import org.jboss.dna.graph.request.UpdatePropertiesRequest; +import org.jboss.dna.graph.request.UuidConflictBehavior; import org.jboss.dna.graph.request.VerifyNodeExistsRequest; import org.jboss.dna.graph.request.VerifyWorkspaceRequest; import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior; @@ -184,13 +185,28 @@ protected void assertNextRequestIsCopy( Location from, Location to ) { + assertNextRequestIsCopy(this.graph.getCurrentWorkspaceName(), from, to); + } + + protected void assertNextRequestIsCopy( String fromWorkspace, + Location from, + Location to ) { + assertNextRequestIsCopy(fromWorkspace, from, to, UuidConflictBehavior.ALWAYS_CREATE_NEW_UUID); + } + + protected void assertNextRequestIsCopy( String fromWorkspace, + Location from, + Location to, + UuidConflictBehavior uuidConflictBehavior) { Request request = executedRequests.poll(); assertThat(request, is(instanceOf(CopyBranchRequest.class))); CopyBranchRequest copy = (CopyBranchRequest)request; + assertThat(copy.fromWorkspace(), is(fromWorkspace)); assertThat(copy.from(), is(from)); assertThat(copy.into(), is(to)); + assertThat(copy.uuidConflictBehavior(), is(uuidConflictBehavior)); } - + protected void assertNextRequestIsDelete( Location at ) { Request request = executedRequests.poll(); assertThat(request, is(instanceOf(DeleteBranchRequest.class))); @@ -377,6 +393,24 @@ } @Test + public void shouldCopyNodeFromOtherWorkspace() { + graph.copy(validPath).fromWorkspace("other").into(validIdProperty1, validIdProperty2); + assertThat(numberOfExecutions, is(1)); + assertNextRequestIsCopy("other", Location.create(validPath), Location.create(validIdProperty1, validIdProperty2)); + assertNoMoreRequests(); + + graph.copy(validPathString).into(validIdProperty1, validIdProperty2); + assertThat(numberOfExecutions, is(1)); + assertNextRequestIsCopy(Location.create(validPath), Location.create(validIdProperty1, validIdProperty2)); + assertNoMoreRequests(); + + graph.copy(validUuid).into(validPath); + assertThat(numberOfExecutions, is(1)); + assertNextRequestIsCopy(Location.create(validUuid), Location.create(validPath)); + assertNoMoreRequests(); + } + + @Test public void shouldDeleteNode() { graph.delete(validPath); assertThat(numberOfExecutions, is(1)); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (revision 1048) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java (working copy) @@ -28,6 +28,7 @@ import java.security.AccessControlException; import java.util.Map; import java.util.Set; +import java.util.UUID; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidSerializedDataException; import javax.jcr.ItemExistsException; @@ -53,6 +54,7 @@ import org.jboss.dna.graph.connector.RepositorySource; import org.jboss.dna.graph.connector.RepositorySourceException; import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; import org.jboss.dna.graph.property.Path; import org.jboss.dna.graph.property.PathFactory; import org.jboss.dna.graph.property.Property; @@ -270,9 +272,25 @@ String destAbsPath ) throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, LockException, RepositoryException { - CheckArg.isNotEmpty(srcAbsPath, "srcAbsPath"); - CheckArg.isNotEmpty(destAbsPath, "destAbsPath"); + this.copy(this.name, srcAbsPath, destAbsPath); + } + /** + * {@inheritDoc} + */ + public void copy( String srcWorkspace, + String srcAbsPath, + String destAbsPath ) + throws ConstraintViolationException, VersionException, AccessDeniedException, PathNotFoundException, ItemExistsException, + LockException, RepositoryException { + CheckArg.isNotNull(srcWorkspace, "source workspace name"); + CheckArg.isNotNull(srcAbsPath, "source path"); + CheckArg.isNotNull(destAbsPath, "destination path"); + + if (!graph.getWorkspaces().contains(srcWorkspace)) { + throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(graph.getSourceName(), this.name)); + } + // Create the paths ... PathFactory factory = context.getValueFactories().getPathFactory(); Path srcPath = null; @@ -294,8 +312,7 @@ } try { - // this.session.checkPermission(srcAbsPath.substring(0, srcAbsPath.lastIndexOf('/')), "remove"); - this.session.checkPermission(destAbsPath, "add_node"); + this.session.checkPermission(destPath.getParent(), "add_node"); } catch (AccessControlException ace) { throw new AccessDeniedException(ace); @@ -305,7 +322,6 @@ * Make sure that the node has a definition at the new location */ SessionCache cache = this.session.cache(); - NodeInfo nodeInfo = cache.findNodeInfo(null, srcPath); NodeInfo cacheParent = cache.findNodeInfo(null, destPath.getParent()); // Skip the cache and load the latest parent info directly from the graph @@ -314,30 +330,22 @@ String parentPath = destPath.getParent().getString(this.context.getNamespaceRegistry()); // This will check for a definition and throw a ConstraintViolationException or ItemExistsException if none is found - this.session.cache().findBestNodeDefinition(parent, parentPath, newNodeName, nodeInfo.getPrimaryTypeName()); + graph.useWorkspace(srcWorkspace); + Property primaryTypeProp = graph.getNodeAt(srcPath).getProperty(JcrLexicon.PRIMARY_TYPE); + Property uuidProp = graph.getNodeAt(srcPath).getProperty(DnaLexicon.UUID); + graph.useWorkspace(this.name); + + assert primaryTypeProp != null : "Cannot have a node in a JCR repository with no jcr:primaryType property"; + assert uuidProp != null : "Cannot have a node in a JCR repository with no UUID"; + NameFactory nameFactory = this.context.getValueFactories().getNameFactory(); + this.session.cache().findBestNodeDefinition(parent, parentPath, newNodeName, nameFactory.create(primaryTypeProp.getFirstValue())); + // Perform the copy operation, but use the "to" form (not the "into", which takes the parent) ... - graph.copy(srcPath).to(destPath); - cache.compensateForWorkspaceChildChange(cacheParent.getUuid(), null, nodeInfo.getUuid(), newNodeName); - - } - - /** - * {@inheritDoc} - */ - public void copy( String srcWorkspace, - String srcAbsPath, - String destAbsPath ) throws NoSuchWorkspaceException { - CheckArg.isNotNull(srcWorkspace, "source workspace"); - CheckArg.isNotNull(srcAbsPath, "source path"); - CheckArg.isNotNull(destAbsPath, "destination path"); + graph.copy(srcPath).fromWorkspace(srcWorkspace).to(destPath); - if (!graph.getWorkspaces().contains(srcWorkspace)) { - throw new NoSuchWorkspaceException(JcrI18n.workspaceNameIsInvalid.text(graph.getSourceName(), this.name)); - } - - - throw new UnsupportedOperationException(); + // Load the node that we just copied + cache.compensateForWorkspaceChildChange(cacheParent.getUuid(), null, UUID.fromString(uuidProp.getFirstValue().toString()), newNodeName); } /** Index: dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java (revision 1047) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/DnaTckTest.java (working copy) @@ -9,7 +9,6 @@ import javax.jcr.Session; import javax.jcr.SimpleCredentials; import org.apache.jackrabbit.test.AbstractJCRTest; -import org.junit.Test; /** * Additional DNA tests that check for JCR compliance. @@ -141,7 +140,6 @@ * Tests that read-write sessions can read nodes by loading all of the children of the root node * @throws Exception */ - @Test public void testShouldAllowReadWriteSessionToRead() throws Exception { session = helper.getReadWriteSession(); testRead(session); @@ -151,7 +149,6 @@ * Tests that read-write sessions can add nodes, remove nodes, set nodes, and set properties. * @throws Exception */ - @Test public void testShouldAllowReadWriteSessionToWrite() throws Exception { session = helper.getReadWriteSession(); testWrite(session); @@ -162,7 +159,6 @@ * workspace. This test makes sure both work. * @throws Exception */ - @Test public void testShouldMapRolesToWorkspacesWhenSpecified() throws Exception { Credentials creds = new SimpleCredentials("defaultonly", "defaultonly".toCharArray()); session = helper.getRepository().login(creds); @@ -183,4 +179,24 @@ session.logout(); } + + public void testShouldCopyFromAnotherWorkspace() throws Exception { + session = helper.getSuperuserSession("otherWorkspace"); + String nodetype1 = this.getProperty("nodetype"); + Node node1 = session.getRootNode().addNode(nodeName1, nodetype1); + node1.addNode(nodeName2, nodetype1); + session.save(); + session.logout(); + + superuser.getRootNode().addNode(nodeName4, nodetype1); + superuser.save(); + + superuser.getWorkspace().copy("otherWorkspace", "/" + nodeName1, "/" + nodeName4 + "/" + nodeName1); + + Node node4 = superuser.getRootNode().getNode(nodeName4); + Node node4node1 = node4.getNode(nodeName1); + Node node4node1node2 = node4node1.getNode(nodeName2); + + assertNotNull(node4node1node2); + } } Index: dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (revision 1047) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/JcrWorkspaceTest.java (working copy) @@ -98,6 +98,7 @@ Graph graph = Graph.create(source, context); graph.create("/a").and().create("/a/b").and().create("/a/b/c").and().create("/b"); graph.set("booleanProperty").on("/a/b").to(true); + graph.set("jcr:primaryType").on("/a/b").to("nt:unstructured"); graph.set("stringProperty").on("/a/b/c").to("value"); // Make sure the path to the namespaces exists ... Index: extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java =================================================================== --- extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java (revision 1049) +++ extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java (working copy) @@ -367,7 +367,8 @@ Location intoLocation = Location.create(intoProjection.pathInSource); String workspaceName = fromProjection.projection.getWorkspaceName(); CopyBranchRequest sourceRequest = new CopyBranchRequest(fromLocation, workspaceName, intoLocation, workspaceName, - request.desiredName(), request.conflictBehavior()); + request.desiredName(), request.nodeConflictBehavior(), + request.uuidConflictBehavior()); execute(sourceRequest, fromProjection.projection); // Copy/transform the results ...