[dna-commits] DNA SVN: r866 - in trunk: dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory and 14 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Thu Apr 30 00:32:27 EDT 2009


Author: rhauch
Date: 2009-04-30 00:32:26 -0400 (Thu, 30 Apr 2009)
New Revision: 866

Modified:
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CreateNodeRequest.java
   trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java
   trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
   trunk/docs/examples/gettingstarted/repositories/src/main/java/org/jboss/example/dna/repository/RepositoryClient.java
   trunk/docs/examples/gettingstarted/repositories/src/main/resources/aircraft.xml
   trunk/docs/examples/gettingstarted/repositories/src/main/resources/cars.xml
   trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientTest.java
   trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientUsingJcrTest.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedWorkspace.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategy.java
   trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategy.java
   trunk/extensions/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties
   trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatingRequestProcessorTest.java
   trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategyTest.java
   trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategyTest.java
   trunk/extensions/dna-connector-federation/src/test/resources/log4j.properties
   trunk/pom.xml
Log:
DNA-387 Federation connector does not support updates, which are required when using with the JCR implementation

Added support for writes in limited scenarios (when the nodes being changed are within a single projection), and addressed a couple of issues related to the handling of UUIDs in the federation connector.  Added more unit tests, verifying the behavior is correct.  Also corrected a few errors in CopyBranchRequest, MoveBranchRequest, and CreateNodeRequest.  Finally, the repository examples (from the Getting Started document) were corrected to use our JCR implementation.

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -263,7 +263,13 @@
                                 InMemoryNode node ) {
             assert context != null;
             assert node != null;
-            assert getRoot().equals(node) != true;
+            if (getRoot().equals(node)) {
+                nodesByUuid.clear();
+                // Create the root node ...
+                InMemoryNode root = new InMemoryNode(rootNodeUuid);
+                nodesByUuid.put(root.getUuid(), root);
+                return;
+            }
             InMemoryNode parent = node.getParent();
             assert parent != null;
             parent.getChildren().remove(node);

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRequestProcessor.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -35,6 +35,7 @@
 import org.jboss.dna.graph.DnaLexicon;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.JcrLexicon;
 import org.jboss.dna.graph.Location;
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.Path;
@@ -150,7 +151,7 @@
         }
         UUID uuid = null;
         for (Property property : request.properties()) {
-            if (property.getName().equals(DnaLexicon.UUID)) {
+            if (property.getName().equals(DnaLexicon.UUID) || property.getName().equals(JcrLexicon.UUID)) {
                 uuid = getExecutionContext().getValueFactories().getUuidFactory().create(property.getValues().next());
                 break;
             }

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CopyBranchRequest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -223,6 +223,7 @@
         if (into.hasPath() && !intoLocation.getPath().getParent().equals(into.getPath())) {
             throw new IllegalArgumentException(GraphI18n.actualLocationIsNotChildOfInputLocation.text(intoLocation, into));
         }
+        this.actualFromLocation = fromLocation;
         this.actualIntoLocation = intoLocation;
     }
 

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CreateNodeRequest.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CreateNodeRequest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/CreateNodeRequest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -168,12 +168,6 @@
         for (Property property : properties) {
             if (property != null) props.add(property);
         }
-        // Add in the location properties ...
-        if (under.hasIdProperties()) {
-            for (Property property : under.getIdProperties()) {
-                if (property != null) props.add(property);
-            }
-        }
         this.properties = Collections.unmodifiableList(props);
     }
 
@@ -207,12 +201,6 @@
             Property property = properties.next();
             if (property != null) props.add(property);
         }
-        // Add in the location properties ...
-        if (under.hasIdProperties()) {
-            for (Property property : under.getIdProperties()) {
-                if (property != null) props.add(property);
-            }
-        }
         this.properties = Collections.unmodifiableList(props);
     }
 

Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/MoveBranchRequest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -237,6 +237,7 @@
         if (!actualNewName.equals(expectedNewName)) {
             throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(newLocation, into));
         }
+        this.actualOldLocation = oldLocation;
         this.actualNewLocation = newLocation;
     }
 
@@ -265,9 +266,8 @@
      */
     public boolean changes( String workspace,
                             Path path ) {
-        return this.workspaceName.equals(workspace) 
-            && (into.hasPath() && into.getPath().isAtOrBelow(path)
-                || from.hasPath() && from.getPath().isAtOrBelow(path)); 
+        return this.workspaceName.equals(workspace)
+               && (into.hasPath() && into.getPath().isAtOrBelow(path) || from.hasPath() && from.getPath().isAtOrBelow(path));
     }
 
     /**

Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepositoryWorkspaceTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -94,9 +94,11 @@
         assertThat(workspace.getRoot().getUuid(), is(rootUuid));
     }
 
-    @Test( expected = AssertionError.class )
-    public void shouldNotAllowRootToBeRemoved() {
+    @Test
+    public void shouldAllowRootToBeRemoved() {
         workspace.removeNode(context, workspace.getRoot());
+        assertThat(workspace.getRoot().getChildren().size(), is(0));
+        assertThat(workspace.getRoot().getProperties().size(), is(0));
     }
 
     @Test( expected = AssertionError.class )

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -141,10 +141,15 @@
                                             context);
         namespaceGraph.useWorkspace(workspaceName);
 
-        Name uriProperty = DnaLexicon.NAMESPACE_URI;
+        // Make sure the "/jcr:system" node exists ...
         PathFactory pathFactory = context.getValueFactories().getPathFactory();
         Path root = pathFactory.createRootPath();
-        Path namespacesPath = context.getValueFactories().getPathFactory().create(root, JcrLexicon.SYSTEM, DnaLexicon.NAMESPACES);
+        Path systemPath = pathFactory.create(root, JcrLexicon.SYSTEM);
+        Property systemPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.SYSTEM);
+        namespaceGraph.createIfMissing(systemPath, systemPrimaryType);
+
+        Name uriProperty = DnaLexicon.NAMESPACE_URI;
+        Path namespacesPath = pathFactory.create(systemPath, DnaLexicon.NAMESPACES);
         PropertyFactory propertyFactory = context.getPropertyFactory();
         Property namespaceType = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.NAMESPACE);
         org.jboss.dna.graph.property.NamespaceRegistry persistentRegistry = new GraphNamespaceRegistry(namespaceGraph,
@@ -168,9 +173,7 @@
         this.queryManager = new JcrQueryManager(this.session);
 
         if (Boolean.valueOf(repository.getOptions().get(Options.PROJECT_NODE_TYPES))) {
-            Path parentOfTypeNodes = context.getValueFactories().getPathFactory().create(root,
-                                                                                         JcrLexicon.SYSTEM,
-                                                                                         JcrLexicon.NODE_TYPES);
+            Path parentOfTypeNodes = context.getValueFactories().getPathFactory().create(systemPath, JcrLexicon.NODE_TYPES);
             repoTypeManager.projectOnto(this.graph, parentOfTypeNodes);
         }
     }

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/MixinTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -115,10 +115,6 @@
         // Set up the initial content ...
         graph = Graph.create(source, context);
 
-        // Make sure the path to the namespaces exists ...
-        graph.create("/jcr:system"); // .and().create("/jcr:system/dna:namespaces");
-        graph.set("jcr:primaryType").on("/jcr:system").to(DnaLexicon.SYSTEM);
-
         // Stub out the connection factory ...
         connectionFactory = new RepositoryConnectionFactory() {
             /**
@@ -318,7 +314,9 @@
     public void shouldAllowAdditionIfResidualChildNodeDoesNotConflict() throws Exception {
         graph.create("/a").and().create("/a/" + CHILD_NODE_B);
         graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a").to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
-        graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry)).on("/a/" + CHILD_NODE_B).to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
+        graph.set(JcrLexicon.PRIMARY_TYPE.getString(registry))
+             .on("/a/" + CHILD_NODE_B)
+             .to(JcrNtLexicon.UNSTRUCTURED.getString(registry));
 
         Node rootNode = session.getRootNode();
         Node nodeA = rootNode.getNode("a");
@@ -672,4 +670,4 @@
 
     }
 
-}
\ No newline at end of file
+}

Modified: trunk/docs/examples/gettingstarted/repositories/src/main/java/org/jboss/example/dna/repository/RepositoryClient.java
===================================================================
--- trunk/docs/examples/gettingstarted/repositories/src/main/java/org/jboss/example/dna/repository/RepositoryClient.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/docs/examples/gettingstarted/repositories/src/main/java/org/jboss/example/dna/repository/RepositoryClient.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -138,6 +138,7 @@
         // Load into the source manager the repository source for the configuration repository ...
         InMemoryRepositorySource configSource = new InMemoryRepositorySource();
         configSource.setName("Configuration");
+        configSource.setDefaultWorkspaceName("default");
         sources.addSource(configSource);
 
         // For this example, we're using a couple of in-memory repositories (including one for the configuration repository).
@@ -145,7 +146,7 @@
         // populate these repositories here by importing from files. First do the configuration repository ...
         String location = this.userInterface.getLocationOfRepositoryFiles();
         Graph config = Graph.create("Configuration", sources, context);
-        config.createWorkspace().named("default");
+        config.useWorkspace("default");
         config.importXmlFrom(location + "/configRepository.xml").into("/");
 
         // Now instantiate the Repository Service ...
@@ -295,6 +296,8 @@
                             children.add(name);
                         }
                     }
+                } catch (javax.jcr.ItemNotFoundException e) {
+                    return false;
                 } catch (javax.jcr.PathNotFoundException e) {
                     return false;
                 } finally {

Modified: trunk/docs/examples/gettingstarted/repositories/src/main/resources/aircraft.xml
===================================================================
--- trunk/docs/examples/gettingstarted/repositories/src/main/resources/aircraft.xml	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/docs/examples/gettingstarted/repositories/src/main/resources/aircraft.xml	2009-04-30 04:32:26 UTC (rev 866)
@@ -24,31 +24,31 @@
   ~ 51 Franklin Street, Fifth Floor
   ~ Boston, MA  02110-1301  USA
   -->
-<Aircraft xmlns:jcr="http://www.jcp.org/jcr/1.0">
+<Aircraft xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0">
     <Business>
-        <aircraft jcr:name="Gulfstream V" maker="Gulfstream" model="G-V" introduced="1995" range="5800nm" cruiseSpeed="488kt" crew="2" emptyWeight="46200lb" url="http://en.wikipedia.org/wiki/Gulfstream_V"/>
-        <aircraft jcr:name="Learjet 45" maker="Learjet" model="LJ45" introduced="1995" numberBuilt="264+" crew="2" emptyWeight="13695lb" range="2120nm" cruiseSpeed="457kt" url="http://en.wikipedia.org/wiki/Learjet_45"/>
+        <nt:unstructured jcr:name="Gulfstream V" maker="Gulfstream" model="G-V" introduced="1995" range="5800nm" cruiseSpeed="488kt" crew="2" emptyWeight="46200lb" url="http://en.wikipedia.org/wiki/Gulfstream_V"/>
+        <nt:unstructured jcr:name="Learjet 45" maker="Learjet" model="LJ45" introduced="1995" numberBuilt="264+" crew="2" emptyWeight="13695lb" range="2120nm" cruiseSpeed="457kt" url="http://en.wikipedia.org/wiki/Learjet_45"/>
     </Business>
     <Commercial>
-        <aircraft jcr:name="Boeing 777" maker="Boeing" model="777-200LR" introduced="1995" numberBuilt="731+" maxRange="7500nm" emptyWeight="326000lb" cruiseSpeed="560mph" url="http://en.wikipedia.org/wiki/Boeing_777"/>
-        <aircraft jcr:name="Boeing 767" maker="Boeing" model="767-200" introduced="1982" numberBuilt="966+" maxRange="3950nm" emptyWeight="176650lb" cruiseSpeed="530mph"  url="http://en.wikipedia.org/wiki/Boeing_767"/>
-        <aircraft jcr:name="Boeing 787" maker="Boeing" model="787-3" introduced="2009" range="3050nm" emptyWeight="223000lb" cruiseSpeed="561mph" url="http://en.wikipedia.org/wiki/Boeing_787"/>
-        <aircraft jcr:name="Boeing 757" maker="Boeing" model="757-200" introduced="1983" numberBuilt="1050" range="3900nm" maxWeight="255000lb" cruiseSpeed="530mph" url="http://en.wikipedia.org/wiki/Boeing_757"/>
-        <aircraft jcr:name="Airbus A380" maker="Airbus" model="A380-800" introduced="2007" numberBuilt="18" range="8200nm" maxWeight="1235000lb" cruiseSpeed="647mph" url="http://en.wikipedia.org/wiki/Airbus_a380"/>
-        <aircraft jcr:name="Airbus A340" maker="Airbus" model="A340-200" introduced="1993" numberBuilt="354" range="8000nm" maxWeight="606300lb" cruiseSpeed="557mph" url="http://en.wikipedia.org/wiki/Airbus_A-340"/>
-        <aircraft jcr:name="Airbus A310" maker="Airbus" model="A310-200" introduced="1983" numberBuilt="255" cruiseSpeed="850km/h" emptyWeight="176312lb" range="3670nm" url="http://en.wikipedia.org/wiki/Airbus_A-310"/>
-        <aircraft jcr:name="Embraer RJ-175" maker="Embraer" model="ERJ170-200" introduced="2004" range="3334km" cruiseSpeed="481kt" emptyWeight="21810kg" url="http://en.wikipedia.org/wiki/EMBRAER_170"/>
+        <nt:unstructured jcr:name="Boeing 777" maker="Boeing" model="777-200LR" introduced="1995" numberBuilt="731+" maxRange="7500nm" emptyWeight="326000lb" cruiseSpeed="560mph" url="http://en.wikipedia.org/wiki/Boeing_777"/>
+        <nt:unstructured jcr:name="Boeing 767" maker="Boeing" model="767-200" introduced="1982" numberBuilt="966+" maxRange="3950nm" emptyWeight="176650lb" cruiseSpeed="530mph"  url="http://en.wikipedia.org/wiki/Boeing_767"/>
+        <nt:unstructured jcr:name="Boeing 787" maker="Boeing" model="787-3" introduced="2009" range="3050nm" emptyWeight="223000lb" cruiseSpeed="561mph" url="http://en.wikipedia.org/wiki/Boeing_787"/>
+        <nt:unstructured jcr:name="Boeing 757" maker="Boeing" model="757-200" introduced="1983" numberBuilt="1050" range="3900nm" maxWeight="255000lb" cruiseSpeed="530mph" url="http://en.wikipedia.org/wiki/Boeing_757"/>
+        <nt:unstructured jcr:name="Airbus A380" maker="Airbus" model="A380-800" introduced="2007" numberBuilt="18" range="8200nm" maxWeight="1235000lb" cruiseSpeed="647mph" url="http://en.wikipedia.org/wiki/Airbus_a380"/>
+        <nt:unstructured jcr:name="Airbus A340" maker="Airbus" model="A340-200" introduced="1993" numberBuilt="354" range="8000nm" maxWeight="606300lb" cruiseSpeed="557mph" url="http://en.wikipedia.org/wiki/Airbus_A-340"/>
+        <nt:unstructured jcr:name="Airbus A310" maker="Airbus" model="A310-200" introduced="1983" numberBuilt="255" cruiseSpeed="850km/h" emptyWeight="176312lb" range="3670nm" url="http://en.wikipedia.org/wiki/Airbus_A-310"/>
+        <nt:unstructured jcr:name="Embraer RJ-175" maker="Embraer" model="ERJ170-200" introduced="2004" range="3334km" cruiseSpeed="481kt" emptyWeight="21810kg" url="http://en.wikipedia.org/wiki/EMBRAER_170"/>
     </Commercial>
     <Vintage>
-        <aircraft jcr:name="Fokker Trimotor" maker="Fokker" model="F.VII" introduced="1925" cruiseSpeed="170km/h" emptyWeight="3050kg" crew="2" url="http://en.wikipedia.org/wiki/Fokker_trimotor"/>
-        <aircraft jcr:name="P-38 Lightning" maker="Lockheed" model="P-38" designedBy="Kelly Johnson" introduced="1941" numberBuilt="10037" rateOfClimb="4750ft/min" range="1300mi" emptyWeight="12780lb" crew="1" url="http://en.wikipedia.org/wiki/P-38_Lightning"/>
-        <aircraft jcr:name="A6M Zero" maker="Mitsubishi" model="A6M" designedBy="Jiro Horikoshi" introduced="1940" numberBuilt="11000" crew="1" emptyWeight="3704lb" serviceCeiling="33000ft" maxSpeed="331mph" range="1929mi" rateOfClimb="3100ft/min" url="http://en.wikipedia.org/wiki/A6M_Zero"/>
-        <aircraft jcr:name="Bf 109" maker="Messerschmitt" model="Bf 109" introduced="1937" url="http://en.wikipedia.org/wiki/BF_109"/>
-        <aircraft jcr:name="Wright Flyer" maker="Wright Brothers" introduced="1903" range="852ft" maxSpeed="30mph" emptyWeight="605lb" crew="1"/>
+        <nt:unstructured jcr:name="Fokker Trimotor" maker="Fokker" model="F.VII" introduced="1925" cruiseSpeed="170km/h" emptyWeight="3050kg" crew="2" url="http://en.wikipedia.org/wiki/Fokker_trimotor"/>
+        <nt:unstructured jcr:name="P-38 Lightning" maker="Lockheed" model="P-38" designedBy="Kelly Johnson" introduced="1941" numberBuilt="10037" rateOfClimb="4750ft/min" range="1300mi" emptyWeight="12780lb" crew="1" url="http://en.wikipedia.org/wiki/P-38_Lightning"/>
+        <nt:unstructured jcr:name="A6M Zero" maker="Mitsubishi" model="A6M" designedBy="Jiro Horikoshi" introduced="1940" numberBuilt="11000" crew="1" emptyWeight="3704lb" serviceCeiling="33000ft" maxSpeed="331mph" range="1929mi" rateOfClimb="3100ft/min" url="http://en.wikipedia.org/wiki/A6M_Zero"/>
+        <nt:unstructured jcr:name="Bf 109" maker="Messerschmitt" model="Bf 109" introduced="1937" url="http://en.wikipedia.org/wiki/BF_109"/>
+        <nt:unstructured jcr:name="Wright Flyer" maker="Wright Brothers" introduced="1903" range="852ft" maxSpeed="30mph" emptyWeight="605lb" crew="1"/>
     </Vintage>
     <Homebuilt>
-        <aircraft jcr:name="Long-EZ" maker="Rutan Aircraft Factory" model="61" emptyWeight="760lb" fuelCapacity="200L" maxSpeed="185kt" since="1976" range="1200nm" url="http://en.wikipedia.org/wiki/Rutan_Long-EZ"/>
-        <aircraft jcr:name="Cirrus VK-30" maker="Cirrus Design" model="VK-30" emptyWeight="2400lb" maxLoad="1200lb" maxSpeed="250mph" rateOfClimb="1500ft/min" range="1300mi" url="http://en.wikipedia.org/wiki/Cirrus_VK-30"/>
-        <aircraft jcr:name="Van's RV-4" maker="Van's Aircraft" model="RV-4" introduced="1980" emptyWeight="905lb" maxLoad="500lb" maxSpeed="200mph" rateOfClimb="2450ft/min" range="725mi" url="http://en.wikipedia.org/wiki/Van%27s_Aircraft_RV-4"/>
+        <nt:unstructured jcr:name="Long-EZ" maker="Rutan Aircraft Factory" model="61" emptyWeight="760lb" fuelCapacity="200L" maxSpeed="185kt" since="1976" range="1200nm" url="http://en.wikipedia.org/wiki/Rutan_Long-EZ"/>
+        <nt:unstructured jcr:name="Cirrus VK-30" maker="Cirrus Design" model="VK-30" emptyWeight="2400lb" maxLoad="1200lb" maxSpeed="250mph" rateOfClimb="1500ft/min" range="1300mi" url="http://en.wikipedia.org/wiki/Cirrus_VK-30"/>
+        <nt:unstructured jcr:name="Van's RV-4" maker="Van's Aircraft" model="RV-4" introduced="1980" emptyWeight="905lb" maxLoad="500lb" maxSpeed="200mph" rateOfClimb="2450ft/min" range="725mi" url="http://en.wikipedia.org/wiki/Van%27s_Aircraft_RV-4"/>
     </Homebuilt>
 </Aircraft>
\ No newline at end of file

Modified: trunk/docs/examples/gettingstarted/repositories/src/main/resources/cars.xml
===================================================================
--- trunk/docs/examples/gettingstarted/repositories/src/main/resources/cars.xml	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/docs/examples/gettingstarted/repositories/src/main/resources/cars.xml	2009-04-30 04:32:26 UTC (rev 866)
@@ -24,25 +24,25 @@
   ~ 51 Franklin Street, Fifth Floor
   ~ Boston, MA  02110-1301  USA
   -->
-<Cars xmlns:jcr="http://www.jcp.org/jcr/1.0">
+<Cars xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0">
     <Hybrid>
-        <car jcr:name="Toyota Prius" maker="Toyota" model="Prius" year="2008" msrp="$21,500" userRating="4.2" valueRating="5" mpgCity="48" mpgHighway="45"/>
-        <car jcr:name="Toyota Highlander" maker="Toyota" model="Highlander" year="2008" msrp="$34,200" userRating="4" valueRating="5" mpgCity="27" mpgHighway="25"/>
-        <car jcr:name="Nissan Altima" maker="Nissan" model="Altima" year="2008" msrp="$18,260" mpgCity="23" mpgHighway="32"/>
+        <nt:unstructured jcr:name="Toyota Prius" maker="Toyota" model="Prius" year="2008" msrp="$21,500" userRating="4.2" valueRating="5" mpgCity="48" mpgHighway="45"/>
+        <nt:unstructured jcr:name="Toyota Highlander" maker="Toyota" model="Highlander" year="2008" msrp="$34,200" userRating="4" valueRating="5" mpgCity="27" mpgHighway="25"/>
+        <nt:unstructured jcr:name="Nissan Altima" maker="Nissan" model="Altima" year="2008" msrp="$18,260" mpgCity="23" mpgHighway="32"/>
     </Hybrid>
     <Sports>
-        <car jcr:name="Aston Martin DB9" maker="Aston Martin" model="DB9" year="2008" msrp="$171,600" userRating="5" mpgCity="12" mpgHighway="19" lengthInInches="185.5" wheelbaseInInches="108.0" engine="5,935 cc 5.9 liters V 12"/>
-        <car jcr:name="Infiniti G37" maker="Infiniti" model="G37" year="2008" msrp="$34,900" userRating="3.5" valueRating="4" mpgCity="18" mpgHighway="24" />
+        <nt:unstructured jcr:name="Aston Martin DB9" maker="Aston Martin" model="DB9" year="2008" msrp="$171,600" userRating="5" mpgCity="12" mpgHighway="19" lengthInInches="185.5" wheelbaseInInches="108.0" engine="5,935 cc 5.9 liters V 12"/>
+        <nt:unstructured jcr:name="Infiniti G37" maker="Infiniti" model="G37" year="2008" msrp="$34,900" userRating="3.5" valueRating="4" mpgCity="18" mpgHighway="24" />
     </Sports>
     <Luxury>
-        <car jcr:name="Cadillac DTS" maker="Cadillac" model="DTS" year="2008" engine="3.6-liter V6" userRating="0"/>
-        <car jcr:name="Bentley Continental" maker="Bentley" model="Continental" year="2008" msrp="$170,990" mpgCity="10" mpgHighway="17" />
-        <car jcr:name="Lexus IS350" maker="Lexus" model="IS350" year="2008" msrp="$36,305" mpgCity="18" mpgHighway="25" userRating="4" valueRating="5" />
+        <nt:unstructured jcr:name="Cadillac DTS" maker="Cadillac" model="DTS" year="2008" engine="3.6-liter V6" userRating="0"/>
+        <nt:unstructured jcr:name="Bentley Continental" maker="Bentley" model="Continental" year="2008" msrp="$170,990" mpgCity="10" mpgHighway="17" />
+        <nt:unstructured jcr:name="Lexus IS350" maker="Lexus" model="IS350" year="2008" msrp="$36,305" mpgCity="18" mpgHighway="25" userRating="4" valueRating="5" />
     </Luxury>
     <Utility>
-        <car jcr:name="Land Rover LR2" maker="Land Rover" model="LR2" year="2008" msrp="$33,985" userRating="4.5" valueRating="5" mpgCity="16" mpgHighway="23" />
-        <car jcr:name="Land Rover LR3" maker="Land Rover" model="LR3" year="2008" msrp="$48,525" userRating="5" valueRating="2" mpgCity="12" mpgHighway="17" />
-        <car jcr:name="Hummer H3" maker="Hummer" model="H3" year="2008" msrp="$30,595" userRating="3.5" valueRating="4" mpgCity="13" mpgHighway="16" />
-        <car jcr:name="Ford F-150" maker="Ford" model="F-150" year="2008" msrp="$23,910" userRating="4" valueRating="1" mpgCity="14" mpgHighway="20" />
+        <nt:unstructured jcr:name="Land Rover LR2" maker="Land Rover" model="LR2" year="2008" msrp="$33,985" userRating="4.5" valueRating="5" mpgCity="16" mpgHighway="23" />
+        <nt:unstructured jcr:name="Land Rover LR3" maker="Land Rover" model="LR3" year="2008" msrp="$48,525" userRating="5" valueRating="2" mpgCity="12" mpgHighway="17" />
+        <nt:unstructured jcr:name="Hummer H3" maker="Hummer" model="H3" year="2008" msrp="$30,595" userRating="3.5" valueRating="4" mpgCity="13" mpgHighway="16" />
+        <nt:unstructured jcr:name="Ford F-150" maker="Ford" model="F-150" year="2008" msrp="$23,910" userRating="4" valueRating="1" mpgCity="14" mpgHighway="20" />
     </Utility>
 </Cars>
\ No newline at end of file

Modified: trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientTest.java
===================================================================
--- trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -139,19 +139,32 @@
         getNodeInfo("Cars", "/Cars");
         assertThat(children, hasItems("Hybrid", "Sports", "Luxury", "Utility"));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(2));
+        switch (getApi()) {
+            case DNA:
+                assertThat(properties.containsKey("dna:uuid"), is(true));
+                assertThat(properties.size(), is(2));
+                break;
+            case JCR:
+                assertThat(properties.size(), is(1));
+                break;
+        }
 
         getNodeInfo("Cars", "/Cars/Hybrid");
         assertThat(children, hasItems("Toyota Prius", "Toyota Highlander", "Nissan Altima"));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(2));
+        switch (getApi()) {
+            case DNA:
+                assertThat(properties.containsKey("dna:uuid"), is(true));
+                assertThat(properties.size(), is(2));
+                break;
+            case JCR:
+                assertThat(properties.size(), is(1));
+                break;
+        }
 
         getNodeInfo("Cars", "/Cars/Sports/Aston Martin DB9");
         assertThat(children.size(), is(0));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
         assertProperty("maker", "Aston Martin");
         assertProperty("maker", "Aston Martin");
         assertProperty("model", "DB9");
@@ -163,7 +176,15 @@
         assertProperty("lengthInInches", "185.5");
         assertProperty("wheelbaseInInches", "108.0");
         assertProperty("engine", "5,935 cc 5.9 liters V 12");
-        assertThat(properties.size(), is(12));
+        switch (getApi()) {
+            case DNA:
+                assertThat(properties.containsKey("dna:uuid"), is(true));
+                assertThat(properties.size(), is(12));
+                break;
+            case JCR:
+                assertThat(properties.size(), is(11));
+                break;
+        }
     }
 
     @Test
@@ -173,8 +194,7 @@
         getNodeInfo("Aircraft", "/Aircraft");
         assertThat(children, hasItems("Business", "Commercial", "Vintage", "Homebuilt"));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(2));
+        // assertThat(properties.containsKey("dna:uuid"), is(true)); // not referenceable in JCR
 
         getNodeInfo("Aircraft", "/Aircraft/Commercial");
         assertThat(children, hasItems("Boeing 777",
@@ -186,20 +206,18 @@
                                       "Airbus A310",
                                       "Embraer RJ-175"));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(2));
+        // assertThat(properties.containsKey("dna:uuid"), is(true)); // not referenceable in JCR
 
         getNodeInfo("Aircraft", "/Aircraft/Vintage/Wright Flyer");
         assertThat(children.size(), is(0));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
+        // assertThat(properties.containsKey("dna:uuid"), is(true));
         assertProperty("maker", "Wright Brothers");
         assertProperty("introduced", "1903");
         assertProperty("range", "852ft");
         assertProperty("maxSpeed", "30mph");
         assertProperty("emptyWeight", "605lb");
         assertProperty("crew", "1");
-        assertThat(properties.size(), is(8));
     }
 
     @Test
@@ -208,29 +226,20 @@
 
         getNodeInfo("Vehicles", "/");
         assertThat(children, hasItems("Vehicles", "jcr:system"));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(1));
 
         getNodeInfo("Vehicles", "/Vehicles");
         assertThat(children, hasItems("Cars", "Aircraft"));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(1));
 
         getNodeInfo("Vehicles", "/");
         assertThat(children, hasItems("Vehicles", "jcr:system"));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(1));
 
         getNodeInfo("Vehicles", "/Vehicles/Cars/Hybrid");
         assertThat(children, hasItems("Toyota Prius", "Toyota Highlander", "Nissan Altima"));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
-        assertThat(properties.size(), is(2));
 
         getNodeInfo("Vehicles", "/Vehicles/Cars/Sports/Aston Martin DB9");
         assertThat(children.size(), is(0));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
         assertProperty("maker", "Aston Martin");
         assertProperty("maker", "Aston Martin");
         assertProperty("model", "DB9");
@@ -242,19 +251,16 @@
         assertProperty("lengthInInches", "185.5");
         assertProperty("wheelbaseInInches", "108.0");
         assertProperty("engine", "5,935 cc 5.9 liters V 12");
-        assertThat(properties.size(), is(12));
 
         getNodeInfo("Vehicles", "/Vehicles/Aircraft/Vintage/Wright Flyer");
         assertThat(children.size(), is(0));
         assertThat(properties.containsKey("jcr:primaryType"), is(true));
-        assertThat(properties.containsKey("dna:uuid"), is(true));
         assertProperty("maker", "Wright Brothers");
         assertProperty("introduced", "1903");
         assertProperty("range", "852ft");
         assertProperty("maxSpeed", "30mph");
         assertProperty("emptyWeight", "605lb");
         assertProperty("crew", "1");
-        assertThat(properties.size(), is(8));
     }
 
     @Test

Modified: trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientUsingJcrTest.java
===================================================================
--- trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientUsingJcrTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/docs/examples/gettingstarted/repositories/src/test/java/org/jboss/example/dna/repository/RepositoryClientUsingJcrTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -39,4 +39,14 @@
     protected Api getApi() {
         return RepositoryClient.Api.JCR;
     }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.example.dna.repository.RepositoryClientTest#shouldHaveContentFromVehiclesRepository()
+     */
+    @Override
+    public void shouldHaveContentFromVehiclesRepository() throws Throwable {
+        super.shouldHaveContentFromVehiclesRepository();
+    }
 }

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedWorkspace.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedWorkspace.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedWorkspace.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -184,6 +184,16 @@
     }
 
     /**
+     * Return the unmodifiable list of projections for the source name.
+     * 
+     * @param sourceName the name of the source
+     * @return the list of projections for this source, or null if there are none
+     */
+    public List<Projection> getProjectionsFor( String sourceName ) {
+        return this.projectionsBySourceName.get(sourceName);
+    }
+
+    /**
      * Determine whether this workspace has a projection supplied contribution
      * 
      * @param sourceName the name of the source

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatingRequestProcessor.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -28,12 +28,14 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
+import net.jcip.annotations.Immutable;
 import net.jcip.annotations.NotThreadSafe;
 import org.jboss.dna.common.i18n.I18n;
 import org.jboss.dna.common.util.CheckArg;
@@ -43,6 +45,7 @@
 import org.jboss.dna.connector.federation.merge.MergePlan;
 import org.jboss.dna.graph.DnaLexicon;
 import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.JcrLexicon;
 import org.jboss.dna.graph.Location;
 import org.jboss.dna.graph.NodeConflictBehavior;
 import org.jboss.dna.graph.cache.CachePolicy;
@@ -56,6 +59,7 @@
 import org.jboss.dna.graph.property.PathFactory;
 import org.jboss.dna.graph.property.PathNotFoundException;
 import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.property.PropertyFactory;
 import org.jboss.dna.graph.request.CloneWorkspaceRequest;
 import org.jboss.dna.graph.request.CompositeRequest;
 import org.jboss.dna.graph.request.CopyBranchRequest;
@@ -70,6 +74,7 @@
 import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
 import org.jboss.dna.graph.request.ReadNodeRequest;
 import org.jboss.dna.graph.request.Request;
+import org.jboss.dna.graph.request.UnsupportedRequestException;
 import org.jboss.dna.graph.request.UpdatePropertiesRequest;
 import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
 import org.jboss.dna.graph.request.processor.RequestProcessor;
@@ -87,6 +92,7 @@
     private final RepositoryConnectionFactory connectionFactory;
     /** The set of all connections, including the cache connection */
     private final Map<String, RepositoryConnection> connectionsBySourceName;
+    protected final PathFactory pathFactory;
     private Logger logger;
 
     /**
@@ -116,6 +122,7 @@
         this.logger = context.getLogger(getClass());
         this.connectionsBySourceName = new HashMap<String, RepositoryConnection>();
         this.defaultWorkspace = defaultWorkspace; // may be null
+        this.pathFactory = context.getValueFactories().getPathFactory();
     }
 
     protected DateTime getCurrentTimeInUtc() {
@@ -248,31 +255,118 @@
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest)
+     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest)
      */
     @Override
-    public void process( CopyBranchRequest request ) {
-        throw new UnsupportedOperationException();
+    public void process( CreateNodeRequest request ) {
+        FederatedWorkspace workspace = getWorkspace(request, request.inWorkspace());
+        if (workspace == null) return;
+
+        // Can push this down if and only if the entire request is within a single federated source ...
+        SingleProjection projection = asSingleProjection(workspace, request.under(), request);
+        if (projection == null) return;
+
+        // Push down the request ...
+        Location parentLocation = Location.create(projection.pathInSource);
+        String workspaceName = projection.projection.getWorkspaceName();
+        CreateNodeRequest sourceRequest = new CreateNodeRequest(parentLocation, workspaceName, request.named(),
+                                                                request.properties());
+        execute(sourceRequest, projection.projection);
+
+        // Copy/transform the results ...
+        if (sourceRequest.hasError()) {
+            request.setError(sourceRequest.getError());
+        } else {
+            request.setActualLocationOfNode(projection.convertToRepository(sourceRequest.getActualLocationOfNode()));
+        }
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest)
+     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest)
      */
     @Override
-    public void process( CreateNodeRequest request ) {
-        throw new UnsupportedOperationException();
+    public void process( DeleteBranchRequest request ) {
+        FederatedWorkspace workspace = getWorkspace(request, request.inWorkspace());
+        if (workspace == null) return;
+
+        // Can push this down if and only if the entire request is within a single federated source ...
+        SingleProjection projection = asSingleProjection(workspace, request.at(), request);
+        if (projection == null) return;
+
+        // Push down the request ...
+        Location sourceLocation = Location.create(projection.pathInSource);
+        String workspaceName = projection.projection.getWorkspaceName();
+        DeleteBranchRequest sourceRequest = new DeleteBranchRequest(sourceLocation, workspaceName);
+        execute(sourceRequest, projection.projection);
+
+        // Copy/transform the results ...
+        if (sourceRequest.hasError()) {
+            request.setError(sourceRequest.getError());
+        } else {
+            request.setActualLocationOfNode(projection.convertToRepository(sourceRequest.getActualLocationOfNode()));
+        }
+
+        // Delete in the cache ...
+        DeleteBranchRequest cacheRequest = new DeleteBranchRequest(request.at(), workspace.getCacheProjection()
+                                                                                          .getWorkspaceName());
+        executeInCache(cacheRequest, workspace);
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest)
+     * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest)
      */
     @Override
-    public void process( DeleteBranchRequest request ) {
-        throw new UnsupportedOperationException();
+    public void process( CopyBranchRequest request ) {
+        FederatedWorkspace fromWorkspace = getWorkspace(request, request.fromWorkspace());
+        if (fromWorkspace == null) return;
+        FederatedWorkspace intoWorkspace = getWorkspace(request, request.intoWorkspace());
+        if (intoWorkspace == null) return;
+        if (!fromWorkspace.equals(intoWorkspace)) {
+            // Otherwise there wasn't a single projection with a single path ...
+            String msg = FederationI18n.unableToPerformOperationSpanningWorkspaces.text(fromWorkspace.getName(),
+                                                                                        intoWorkspace.getName());
+            request.setError(new UnsupportedRequestException(msg));
+        }
+
+        // Can push this down if and only if the entire request is within a single federated source ...
+        SingleProjection fromProjection = asSingleProjection(fromWorkspace, request.from(), request);
+        if (fromProjection == null) return;
+        SingleProjection intoProjection = asSingleProjection(intoWorkspace, request.into(), request);
+        if (intoProjection == null) return;
+        if (!intoProjection.projection.equals(fromProjection.projection)) {
+            // Otherwise there wasn't a single projection with a single path ...
+            String msg = FederationI18n.unableToPerformOperationUnlessLocationsAreFromSingleProjection.text(request.from(),
+                                                                                                            request.into(),
+                                                                                                            fromWorkspace.getName(),
+                                                                                                            fromProjection.projection.getRules(),
+                                                                                                            intoProjection.projection.getRules());
+            request.setError(new UnsupportedRequestException(msg));
+        }
+
+        // Push down the request ...
+        Location fromLocation = Location.create(fromProjection.pathInSource);
+        Location intoLocation = Location.create(intoProjection.pathInSource);
+        String workspaceName = fromProjection.projection.getWorkspaceName();
+        CopyBranchRequest sourceRequest = new CopyBranchRequest(fromLocation, workspaceName, intoLocation, workspaceName,
+                                                                request.desiredName(), request.conflictBehavior());
+        execute(sourceRequest, fromProjection.projection);
+
+        // Copy/transform the results ...
+        if (sourceRequest.hasError()) {
+            request.setError(sourceRequest.getError());
+        } else {
+            request.setActualLocations(fromProjection.convertToRepository(sourceRequest.getActualLocationBefore()),
+                                       intoProjection.convertToRepository(sourceRequest.getActualLocationAfter()));
+        }
+
+        // Delete from the cache the parent of the new location ...
+        DeleteBranchRequest cacheRequest = new DeleteBranchRequest(request.into(), fromWorkspace.getCacheProjection()
+                                                                                                .getWorkspaceName());
+        executeInCache(cacheRequest, fromWorkspace);
     }
 
     /**
@@ -282,7 +376,43 @@
      */
     @Override
     public void process( MoveBranchRequest request ) {
-        throw new UnsupportedOperationException();
+        FederatedWorkspace workspace = getWorkspace(request, request.inWorkspace());
+        if (workspace == null) return;
+
+        // Can push this down if and only if the entire request is within a single federated source ...
+        SingleProjection fromProjection = asSingleProjection(workspace, request.from(), request);
+        if (fromProjection == null) return;
+        SingleProjection intoProjection = asSingleProjection(workspace, request.into(), request);
+        if (intoProjection == null) return;
+        if (!intoProjection.projection.equals(fromProjection.projection)) {
+            // Otherwise there wasn't a single projection with a single path ...
+            String msg = FederationI18n.unableToPerformOperationUnlessLocationsAreFromSingleProjection.text(request.from(),
+                                                                                                            request.into(),
+                                                                                                            workspace.getName(),
+                                                                                                            fromProjection.projection.getRules(),
+                                                                                                            intoProjection.projection.getRules());
+            request.setError(new UnsupportedRequestException(msg));
+        }
+
+        // Push down the request ...
+        Location fromLocation = Location.create(fromProjection.pathInSource);
+        Location intoLocation = Location.create(intoProjection.pathInSource);
+        String workspaceName = fromProjection.projection.getWorkspaceName();
+        MoveBranchRequest sourceRequest = new MoveBranchRequest(fromLocation, intoLocation, workspaceName, request.desiredName(),
+                                                                request.conflictBehavior());
+        execute(sourceRequest, fromProjection.projection);
+
+        // Copy/transform the results ...
+        if (sourceRequest.hasError()) {
+            request.setError(sourceRequest.getError());
+        } else {
+            request.setActualLocations(fromProjection.convertToRepository(sourceRequest.getActualLocationBefore()),
+                                       intoProjection.convertToRepository(sourceRequest.getActualLocationAfter()));
+        }
+        // Delete from the cache ...
+        DeleteBranchRequest cacheRequest = new DeleteBranchRequest(request.from(), workspace.getCacheProjection()
+                                                                                            .getWorkspaceName());
+        executeInCache(cacheRequest, workspace);
     }
 
     /**
@@ -292,7 +422,31 @@
      */
     @Override
     public void process( UpdatePropertiesRequest request ) {
-        throw new UnsupportedOperationException();
+        FederatedWorkspace workspace = getWorkspace(request, request.inWorkspace());
+        if (workspace == null) return;
+
+        // Can push this down if and only if the entire request is within a single federated source ...
+        SingleProjection projection = asSingleProjection(workspace, request.on(), request);
+        if (projection == null) return;
+
+        // Push down the request ...
+        Location sourceLocation = Location.create(projection.pathInSource);
+        String workspaceName = projection.projection.getWorkspaceName();
+        UpdatePropertiesRequest sourceRequest = new UpdatePropertiesRequest(sourceLocation, workspaceName, request.properties());
+        execute(sourceRequest, projection.projection);
+
+        // Copy/transform the results ...
+        if (sourceRequest.hasError()) {
+            request.setError(sourceRequest.getError());
+        } else {
+            request.setActualLocationOfNode(projection.convertToRepository(sourceRequest.getActualLocationOfNode()));
+        }
+
+        // Update the cache ...
+        UpdatePropertiesRequest cacheRequest = new UpdatePropertiesRequest(request.on(), workspace.getCacheProjection()
+                                                                                                  .getWorkspaceName(),
+                                                                           request.properties());
+        executeInCache(cacheRequest, workspace);
     }
 
     /**
@@ -305,7 +459,7 @@
         FederatedWorkspace workspace = getWorkspace(request, request.workspaceName());
         if (workspace != null) {
             request.setActualWorkspaceName(workspace.getName());
-            Location root = Location.create(getExecutionContext().getValueFactories().getPathFactory().createRootPath());
+            Location root = Location.create(pathFactory.createRootPath());
             ReadNodeRequest nodeInfo = getNode(root, workspace);
             if (nodeInfo.hasError()) return;
             request.setActualRootLocation(nodeInfo.getActualLocationOfNode());
@@ -353,6 +507,96 @@
         throw new UnsupportedOperationException();
     }
 
+    @Immutable
+    protected class SingleProjection {
+        protected final Projection projection;
+        protected final Path pathInSource;
+        protected final Location federatedLocation;
+
+        protected SingleProjection( Projection projection,
+                                    Path pathInSource,
+                                    Location federatedLocation ) {
+            this.projection = projection;
+            this.federatedLocation = federatedLocation;
+            this.pathInSource = pathInSource;
+        }
+
+        protected Location convertToRepository( Location sourceLocation ) {
+            assert sourceLocation != null;
+            if (sourceLocation.hasPath()) {
+                Set<Path> paths = projection.getPathsInRepository(sourceLocation.getPath(), pathFactory);
+                assert paths.size() == 1;
+                return sourceLocation.with(paths.iterator().next());
+            }
+            return sourceLocation;
+        }
+    }
+
+    protected SingleProjection asSingleProjection( FederatedWorkspace federatedWorkspace,
+                                                   Location location,
+                                                   Request request ) {
+        // Check the cache for this location ...
+        ReadNodeRequest nodeInfo = getNode(location, federatedWorkspace);
+        if (nodeInfo.hasError()) {
+            request.setError(nodeInfo.getError());
+            return null;
+        }
+        Location actualLocation = nodeInfo.getActualLocationOfNode();
+        Path pathInRepository = actualLocation.getPath();
+        assert pathInRepository != null;
+
+        // Get the merge plan for the node ...
+        MergePlan plan = getMergePlan(nodeInfo);
+        assert plan != null;
+        if (plan.getRealContributionCount() == 1) {
+            for (Contribution contribution : plan) {
+                if (contribution.isEmpty() || contribution.isPlaceholder()) continue;
+                for (Projection projection : federatedWorkspace.getProjectionsFor(contribution.getSourceName())) {
+                    Set<Path> paths = projection.getPathsInSource(pathInRepository, pathFactory);
+                    if (paths.size() == 1) {
+                        return new SingleProjection(projection, paths.iterator().next(), actualLocation);
+                    }
+                }
+            }
+        }
+
+        // Otherwise there wasn't a single projection with a single path ...
+        StringBuilder projections = new StringBuilder();
+        boolean first = true;
+        for (Contribution contribution : plan) {
+            if (contribution.isPlaceholder() || contribution.isEmpty()) continue;
+            if (first) first = false;
+            else projections.append(", ");
+            for (Projection projection : federatedWorkspace.getProjectionsFor(contribution.getSourceName())) {
+                Set<Path> paths = projection.getPathsInSource(pathInRepository, pathFactory);
+                if (paths.size() == 1) {
+                    projections.append(FederationI18n.pathInProjection.text(paths.iterator().next(), projection.getRules()));
+                } else {
+                    projections.append(FederationI18n.pathInProjection.text(paths, projection.getRules()));
+                }
+            }
+        }
+        String msg = FederationI18n.unableToPerformOperationUnlessLocationIsFromSingleProjection.text(location,
+                                                                                                      federatedWorkspace.getName(),
+                                                                                                      projections);
+        request.setError(new UnsupportedRequestException(msg));
+        return null;
+    }
+
+    protected void execute( Request request,
+                            Projection projection ) {
+        RepositoryConnection connection = getConnection(projection);
+        connection.execute(getExecutionContext(), request);
+        // Don't need to close, as we'll close all connections when this processor is closed
+    }
+
+    protected void executeInCache( Request request,
+                                   FederatedWorkspace workspace ) {
+        RepositoryConnection connection = getConnectionToCacheFor(workspace);
+        connection.execute(getExecutionContext(), request);
+        // Don't need to close, as we'll close all connections when this processor is closed
+    }
+
     /**
      * Get the node information from the underlying sources or, if possible, from the cache.
      * 
@@ -363,12 +607,9 @@
      */
     protected ReadNodeRequest getNode( Location location,
                                        FederatedWorkspace workspace ) throws RepositorySourceException {
-        final ExecutionContext context = getExecutionContext();
-
         // Check the cache first ...
-        RepositoryConnection cacheConnection = getConnectionToCacheFor(workspace);
         ReadNodeRequest fromCache = new ReadNodeRequest(location, workspace.getCacheProjection().getWorkspaceName());
-        cacheConnection.execute(context, fromCache);
+        executeInCache(fromCache, workspace);
 
         // Look at the cache results from the cache for problems, or if found a plan in the cache look
         // at the contributions. We'll be putting together the set of source names for which we need to
@@ -395,13 +636,19 @@
                         Location locationToLoad = Location.create(pathToLoad);
                         loadContributionsFromSources(locationToLoad, workspace, null, contributions); // sourceNames may be
                         // null or empty
-                        FederatedNode mergedNode = createFederatedNode(locationToLoad, workspace, contributions, true);
+                        FederatedNode mergedNode = createFederatedNode(locationToLoad, workspace, fromCache, contributions, true);
                         if (mergedNode == null) {
                             // No source had a contribution ...
                             I18n msg = FederationI18n.nodeDoesNotExistAtPath;
                             fromCache.setError(new PathNotFoundException(location, ancestor, msg.text(path, ancestor)));
                             return fromCache;
                         }
+                        MergePlan mergePlan = mergedNode.getMergePlan();
+                        if (mergePlan != null) {
+                            Property mergePlanProperty = getExecutionContext().getPropertyFactory().create(DnaLexicon.MERGE_PLAN,
+                                                                                                           (Object)mergePlan);
+                            fromCache.addProperty(mergePlanProperty);
+                        }
                         contributions.clear();
                         // Move to the next child along the path ...
                         pathToLoad = pathToLoad.getParent();
@@ -449,7 +696,7 @@
             location = fromCache.at();
         }
         loadContributionsFromSources(location, workspace, sourceNames, contributions); // sourceNames may be null or empty
-        FederatedNode mergedNode = createFederatedNode(location, workspace, contributions, true);
+        FederatedNode mergedNode = createFederatedNode(location, workspace, fromCache, contributions, true);
         if (mergedNode == null) {
             // No source had a contribution ...
             if (location.hasPath()) {
@@ -467,6 +714,7 @@
 
     protected FederatedNode createFederatedNode( Location location,
                                                  FederatedWorkspace federatedWorkspace,
+                                                 ReadNodeRequest fromCache,
                                                  List<Contribution> contributions,
                                                  boolean updateCache ) throws RepositorySourceException {
         assert location != null;
@@ -492,7 +740,7 @@
         // Create the node, and use the existing UUID if one is found in the cache ...
         ExecutionContext context = getExecutionContext();
         assert context != null;
-        FederatedNode mergedNode = new FederatedNode(location.with((UUID)null), federatedWorkspace.getName());
+        FederatedNode mergedNode = new FederatedNode(location, federatedWorkspace.getName());
 
         // Merge the results into a single set of results ...
         assert contributions.size() > 0;
@@ -502,7 +750,7 @@
         }
         if (updateCache) {
             // Place the results into the cache ...
-            updateCache(federatedWorkspace, mergedNode);
+            updateCache(federatedWorkspace, mergedNode, fromCache);
         }
         // And return the results ...
         return mergedNode;
@@ -525,7 +773,6 @@
                                                  List<Contribution> contributions ) throws RepositorySourceException {
         // At this point, there is no merge plan, so read information from the sources ...
         final ExecutionContext context = getExecutionContext();
-        final PathFactory pathFactory = context.getValueFactories().getPathFactory();
 
         CachePolicy cachePolicy = federatedWorkspace.getCachePolicy();
         // If the location has no path, then we have to submit a request to ALL sources ...
@@ -720,9 +967,9 @@
     }
 
     protected void updateCache( FederatedWorkspace federatedWorkspace,
-                                FederatedNode mergedNode ) throws RepositorySourceException {
+                                FederatedNode mergedNode,
+                                ReadNodeRequest fromCache ) throws RepositorySourceException {
         final ExecutionContext context = getExecutionContext();
-        final RepositoryConnection cacheConnection = getConnectionToCacheFor(federatedWorkspace);
         final Location location = mergedNode.at();
         final Path path = location.getPath();
         final String cacheWorkspace = federatedWorkspace.getCacheProjection().getWorkspaceName();
@@ -733,29 +980,126 @@
         // If the merged node has a merge plan, then add it to the properties if it is not already there ...
         Map<Name, Property> properties = mergedNode.getPropertiesByName();
         MergePlan mergePlan = mergedNode.getMergePlan();
-        if (mergePlan != null) {
+        if (mergePlan != null && !properties.containsKey(DnaLexicon.MERGE_PLAN)) {
             // Record the merge plan on the merged node ...
             Property mergePlanProperty = getExecutionContext().getPropertyFactory().create(DnaLexicon.MERGE_PLAN,
                                                                                            (Object)mergePlan);
             properties.put(mergePlanProperty.getName(), mergePlanProperty);
         }
 
-        if (path.isRoot()) {
-            // Set the properties ...
-            requests.add(new UpdatePropertiesRequest(location, cacheWorkspace, properties));
-        } else {
-            // This is not the root node, so we need to create the node ...
+        // Make sure the UUID is being stored ...
+        PropertyFactory propertyFactory = getExecutionContext().getPropertyFactory();
+        Property uuidProperty = properties.get(DnaLexicon.UUID);
+        if (uuidProperty == null) uuidProperty = properties.get(JcrLexicon.UUID);
+        if (uuidProperty == null) {
+            UUID uuid = mergedNode.at().getUuid();
+            if (uuid == null) uuid = UUID.randomUUID();
+            uuidProperty = propertyFactory.create(DnaLexicon.UUID, uuid);
+            properties.put(uuidProperty.getName(), uuidProperty);
+        }
+
+        // Have the children changed ...
+        if (mergedNode.hasError() && !path.isRoot()) {
+            // This is not the root node, so we need to create the node (or replace it if it exists) ...
             final Location parentLocation = Location.create(path.getParent());
             childName = path.getLastSegment().getName();
             requests.add(new CreateNodeRequest(parentLocation, cacheWorkspace, childName, NodeConflictBehavior.REPLACE,
                                                mergedNode.getProperties()));
+            // logger.trace("Adding {0} to cache with properties {1}", location, properties);
+            // Now create all of the children that this federated node knows of ...
+            for (Location child : mergedNode.getChildren()) {
+                childName = child.getPath().getLastSegment().getName();
+                requests.add(new CreateNodeRequest(location, cacheWorkspace, childName, NodeConflictBehavior.APPEND, child));
+                // logger.trace("Caching child of {0} named {1}", location, childName);
+            }
+        } else if (fromCache.getChildren().equals(mergedNode.getChildren())) {
+            // Just update the properties ...
+            requests.add(new UpdatePropertiesRequest(location, cacheWorkspace, properties));
+            // logger.trace("Updating cached properties on the root to {0}", properties);
+        } else {
+            // The children have changed, so figure out how ...
+            if (fromCache.getChildren().isEmpty()) {
+                // No children in the cache, so just update the properties of the node ...
+                requests.add(new UpdatePropertiesRequest(location, cacheWorkspace, properties));
+                // logger.trace("Updating cached properties on {0} to {1}", location, properties);
+
+                // And create all of the children that this federated node knows of ...
+                for (Location child : mergedNode.getChildren()) {
+                    childName = child.getPath().getLastSegment().getName();
+                    requests.add(new CreateNodeRequest(location, cacheWorkspace, childName, NodeConflictBehavior.APPEND, child));
+                    // logger.trace("Caching child of {0} named {1}", location, childName);
+                }
+            } else if (mergedNode.getChildren().isEmpty()) {
+                // There were children in the cache but not in the merged node, so update the cached properties
+                requests.add(new UpdatePropertiesRequest(location, cacheWorkspace, properties));
+
+                // and delete all the children ...
+                for (Location child : fromCache.getChildren()) {
+                    requests.add(new DeleteBranchRequest(child, cacheWorkspace));
+                    // logger.trace("Removing {0} from cache", child);
+                }
+            } else {
+                // There were children in the cache and in the merged node. The easy way is to just remove the
+                // branch from the cache, the create it again ...
+                if (path.isRoot()) {
+                    requests.add(new UpdatePropertiesRequest(location, cacheWorkspace, properties));
+                    // logger.trace("Updating cached properties on {0} to {1}", location, properties);
+
+                    // and delete all the children ...
+                    for (Location child : fromCache.getChildren()) {
+                        requests.add(new DeleteBranchRequest(child, cacheWorkspace));
+                        // logger.trace("Removing child node {0} from cache", child);
+                    }
+
+                    // Now create all of the children that this federated node knows of ...
+                    for (Location child : mergedNode.getChildren()) {
+                        childName = child.getPath().getLastSegment().getName();
+                        requests.add(new CreateNodeRequest(location, cacheWorkspace, childName, NodeConflictBehavior.APPEND,
+                                                           child));
+                        // logger.trace("Caching child of {0} named {1}", location, childName);
+                    }
+                } else {
+                    requests.add(new DeleteBranchRequest(location, cacheWorkspace));
+                    // logger.trace("Replacing node {0} from cache", location);
+
+                    // This is not the root node, so we need to create the node (or replace it if it exists) ...
+                    final Location parentLocation = Location.create(path.getParent());
+                    childName = path.getLastSegment().getName();
+                    requests.add(new CreateNodeRequest(parentLocation, cacheWorkspace, childName, NodeConflictBehavior.REPLACE,
+                                                       mergedNode.getProperties()));
+                    // logger.trace("Adding {0} to cache with properties {1}", location, properties);
+                    // Now create all of the children that this federated node knows of ...
+                    for (Location child : mergedNode.getChildren()) {
+                        childName = child.getPath().getLastSegment().getName();
+                        requests.add(new CreateNodeRequest(location, cacheWorkspace, childName, NodeConflictBehavior.APPEND,
+                                                           child));
+                        // logger.trace("Caching child of {0} named {1}", location, childName);
+                    }
+                }
+            }
         }
 
-        // Now create all of the children that this federated node knows of ...
-        for (Location child : mergedNode.getChildren()) {
-            childName = child.getPath().getLastSegment().getName();
-            requests.add(new CreateNodeRequest(location, cacheWorkspace, childName, NodeConflictBehavior.APPEND));
+        // Execute all the requests ...
+        final RepositoryConnection cacheConnection = getConnectionToCacheFor(federatedWorkspace);
+        cacheConnection.execute(context, CompositeRequest.with(requests));
+
+        // If the children did not have UUIDs, then find the actual locations for each of the cached nodes ...
+        if (requests.size() > 1) {
+            Iterator<Request> requestIter = requests.iterator();
+            requestIter.next(); // Skip the first request, which creates/updates the node (we want children)
+            List<Location> children = mergedNode.getChildren();
+            for (int i = 0; i != children.size(); ++i) {
+                Request request = requestIter.next();
+                while (!(request instanceof CreateNodeRequest)) { // skip non-create requests
+                    request = requestIter.next();
+                }
+                Location actual = ((CreateNodeRequest)request).getActualLocationOfNode();
+                Location child = children.get(i);
+                if (!child.hasIdProperties()) {
+                    assert child.getPath().equals(actual.getPath());
+                    children.set(i, actual);
+                }
+            }
         }
-        cacheConnection.execute(context, CompositeRequest.with(requests));
     }
 }

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -43,6 +43,11 @@
     public static I18n unableToAuthenticateConnectionToFederatedRepository;
     public static I18n repositoryHasBeenShutDown;
     public static I18n errorAddingProjectionRuleParseMethod;
+    public static I18n pathInProjection;
+    public static I18n pathsInProjection;
+    public static I18n unableToPerformOperationUnlessLocationIsFromSingleProjection;
+    public static I18n unableToPerformOperationUnlessLocationsAreFromSingleProjection;
+    public static I18n unableToPerformOperationSpanningWorkspaces;
 
     public static I18n workspaceDoesNotExist;
     public static I18n noDefaultWorkspace;

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -242,6 +242,21 @@
     public abstract int getContributionCount();
 
     /**
+     * Get the number of contributions that are not placeholders or empty.
+     * 
+     * @return the number of real contributions
+     */
+    public int getRealContributionCount() {
+        int count = 0;
+        for (Contribution contribution : this) {
+            if (contribution.isPlaceholder()) continue;
+            if (contribution.isEmpty()) continue;
+            ++count;
+        }
+        return count;
+    }
+
+    /**
      * Get the plan annotation property with the given name. Plan annotations are custom properties that may be set by
      * MergeProcessor implementations to store custom properties on the plan. This method does nothing if the supplied name is
      * null

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategy.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategy.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategy.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -91,26 +91,32 @@
         }
         if (dnaUuidProperty != null) uuidProperty = dnaUuidProperty; // use "dna:uuid" if there is one
 
-        // Look for the UUID property on the properties, and update the federated node ...
-        if (uuidProperty != null && !uuidProperty.isEmpty()) {
-            UUID uuid = context.getValueFactories().getUuidFactory().create(uuidProperty.getFirstValue());
-            uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, uuid); // Use the "dna:uuid" name
-            federatedNode.setActualLocationOfNode(federatedNode.at().with(uuidProperty));
+        if (federatedNode.at().getUuid() != null) {
+            federatedNode.setActualLocationOfNode(federatedNode.at());
         } else {
-            // Make sure there's a UUID for an identification property ...
-            if (location.getUuid() == null) {
-                location = location.with(UUID.randomUUID());
+            // Look for the UUID property on the properties, and update the federated node ...
+            if (uuidProperty != null && !uuidProperty.isEmpty()) {
+                UUID uuid = context.getValueFactories().getUuidFactory().create(uuidProperty.getFirstValue());
+                uuidProperty = context.getPropertyFactory().create(DnaLexicon.UUID, uuid); // Use the "dna:uuid" name
+                federatedNode.setActualLocationOfNode(federatedNode.at().with(uuidProperty));
+            } else {
+                // Make sure there's a UUID for an identification property ...
+                if (location.getUuid() == null) {
+                    location = location.with(UUID.randomUUID());
+                }
+                // Set the UUID as a property (it wasn't set already) ...
+                uuidProperty = location.getIdProperty(DnaLexicon.UUID);
+                assert uuidProperty != null; // there should be one!
+                federatedNode.addProperty(uuidProperty);
+                federatedNode.setActualLocationOfNode(location);
             }
-            // Set the UUID as a property (it wasn't set already) ...
-            uuidProperty = location.getIdProperty(DnaLexicon.UUID);
-            assert uuidProperty != null; // there should be one!
-            federatedNode.addProperty(uuidProperty);
-            federatedNode.setActualLocationOfNode(location);
         }
 
         // Assign the merge plan ...
         MergePlan mergePlan = MergePlan.create(contributions);
         federatedNode.setMergePlan(mergePlan);
+        Property mergePlanProperty = context.getPropertyFactory().create(DnaLexicon.MERGE_PLAN, (Object)mergePlan);
+        federatedNode.addProperty(mergePlanProperty);
     }
 
     private boolean hasUuidValue( ExecutionContext context,

Modified: trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategy.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategy.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategy.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -110,7 +110,7 @@
                     if (!childNames.containsKey(childName)) {
                         childNames.put(childName, 1);
                         Path pathToChild = pathFactory.create(location.getPath(), childName);
-                        federatedNode.addChild(Location.create(pathToChild));
+                        federatedNode.addChild(child.with(pathToChild));
                     }
                 }
             } else {
@@ -140,7 +140,7 @@
                         index = previousValue;
                     }
                     Path pathToChild = pathFactory.create(location.getPath(), childName, index);
-                    federatedNode.addChild(Location.create(pathToChild));
+                    federatedNode.addChild(child.with(pathToChild)); // keep the same identifiers
                 }
 
                 // Add in the properties ...
@@ -152,7 +152,7 @@
 
                     // Record the property ...
                     Property existing = properties.put(property.getName(), property);
-                    if (existing != null) {
+                    if (existing != null && !existing.equals(property)) {
                         // There's already an existing property, so we need to merge them ...
                         Property merged = merge(existing, property, context.getPropertyFactory(), removeDuplicateProperties);
                         properties.put(property.getName(), merged);
@@ -181,6 +181,8 @@
         // Assign the merge plan ...
         MergePlan mergePlan = MergePlan.create(contributions);
         federatedNode.setMergePlan(mergePlan);
+        Property mergePlanProperty = context.getPropertyFactory().create(DnaLexicon.MERGE_PLAN, (Object)mergePlan);
+        properties.put(mergePlanProperty.getName(), mergePlanProperty);
     }
 
     /**

Modified: trunk/extensions/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties
===================================================================
--- trunk/extensions/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties	2009-04-30 04:32:26 UTC (rev 866)
@@ -32,6 +32,11 @@
 unableToAuthenticateConnectionToFederatedRepository = Unable to authenticate "{1}" for federated repository "{0}"
 repositoryHasBeenShutDown = The "{0}" federated repository has been shut down and may no longer be used.
 errorAddingProjectionRuleParseMethod = Error while adding a parsing method for a federation projection rule
+pathInProjection = path "{0}" in projection {1}
+pathsInProjection = paths "{0}" in projection {1}
+unableToPerformOperationUnlessLocationIsFromSingleProjection = The node "{0}" in workspace "{1}" must be within a single projection for this operation to succeed, but is in {2}
+unableToPerformOperationUnlessLocationsAreFromSingleProjection = The nodes "{0}" and "{1}" in workspace "{2}" must both be within a single projection for this operation to succeed, but come from projections "{3}" and "{4}"
+unableToPerformOperationSpanningWorkspaces = Unable to perform operation spanning workspaces ("{0}" and "{1}")
 
 workspaceDoesNotExist = The "{0}" federated repository does not have a workspace named "{1}"
 noDefaultWorkspace = The "{0}" federated repository does not have a default workspace
\ No newline at end of file

Modified: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatingRequestProcessorTest.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatingRequestProcessorTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatingRequestProcessorTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -41,10 +41,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
-import org.jboss.dna.connector.federation.FederatedWorkspace;
-import org.jboss.dna.connector.federation.FederatingRequestProcessor;
-import org.jboss.dna.connector.federation.Projection;
-import org.jboss.dna.connector.federation.ProjectionParser;
 import org.jboss.dna.connector.federation.contribution.Contribution;
 import org.jboss.dna.graph.ExecutionContext;
 import org.jboss.dna.graph.Graph;
@@ -55,8 +51,19 @@
 import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
 import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
 import org.jboss.dna.graph.property.DateTime;
+import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.Path;
 import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.request.CopyBranchRequest;
+import org.jboss.dna.graph.request.CreateNodeRequest;
+import org.jboss.dna.graph.request.DeleteBranchRequest;
+import org.jboss.dna.graph.request.MoveBranchRequest;
+import org.jboss.dna.graph.request.ReadAllChildrenRequest;
+import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
+import org.jboss.dna.graph.request.ReadPropertyRequest;
+import org.jboss.dna.graph.request.UnsupportedRequestException;
+import org.jboss.dna.graph.request.UpdatePropertiesRequest;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.MockitoAnnotations;
@@ -93,6 +100,7 @@
         cachePolicy = new BasicCachePolicy(219L, TimeUnit.SECONDS);
         cacheSource = new InMemoryRepositorySource();
         cacheSource.setName("Cache");
+        cacheSource.setDefaultWorkspaceName("cacheSpace");
         ProjectionParser ruleParser = ProjectionParser.getInstance();
         cacheProjectionRules = ruleParser.rulesFromStrings(context, "/ => /cache/repo/A");
         cacheProjection = new Projection(cacheSource.getName(), "cacheSpace", cacheProjectionRules);
@@ -128,6 +136,82 @@
         doReturn(cacheSource.getConnection()).when(connectionFactory).createConnection(cacheSource.getName());
     }
 
+    protected Name name( String name ) {
+        return context.getValueFactories().getNameFactory().create(name);
+    }
+
+    protected Path path( String path ) {
+        return pathFactory.create(path);
+    }
+
+    protected Path.Segment segment( String path ) {
+        return pathFactory.createSegment(path);
+    }
+
+    protected Property property( String name,
+                                 Object... values ) {
+        return context.getPropertyFactory().create(name(name), values);
+    }
+
+    protected Map<Name, Property> addProperty( Map<Name, Property> properties,
+                                               String name,
+                                               Object... values ) {
+        Property property = property(name, values);
+        properties.put(property.getName(), property);
+        return properties;
+    }
+
+    protected void assertNodeHasChildren( String path,
+                                          String... expectedChildSegments ) {
+        Location parent = Location.create(path(path));
+        ReadAllChildrenRequest request = new ReadAllChildrenRequest(parent, "fedSpace");
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path(path)));
+        List<Location> actualChildLocations = request.getChildren();
+        Path.Segment[] actualChildren = new Path.Segment[actualChildLocations.size()];
+        for (int i = 0; i != actualChildren.length; ++i) {
+            Path actualChildPath = actualChildLocations.get(i).getPath();
+            actualChildren[i] = actualChildPath.getLastSegment();
+            assertThat(actualChildPath.getParent(), is(path(path)));
+        }
+        Path.Segment[] expectedChildren = new Path.Segment[expectedChildSegments.length];
+        for (int i = 0; i != expectedChildren.length; ++i) {
+            expectedChildren[i] = segment(expectedChildSegments[i]);
+        }
+        assertThat(actualChildren, is(expectedChildren));
+    }
+
+    protected void assertNodeHasProperty( String path,
+                                          String propertyName,
+                                          Object... expectedValues ) {
+        Location parent = Location.create(path(path));
+        ReadPropertyRequest request = new ReadPropertyRequest(parent, "fedSpace", name(propertyName));
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path(path)));
+        Object[] actualValues = request.getProperty().getValuesAsArray();
+        String[] actualValuesAsStrings = new String[actualValues.length];
+        for (int i = 0; i != actualValues.length; ++i) {
+            actualValuesAsStrings[i] = context.getValueFactories().getStringFactory().create(actualValues[i]);
+        }
+        String[] expectedValuesAsStrings = new String[expectedValues.length];
+        for (int i = 0; i != expectedValues.length; ++i) {
+            expectedValuesAsStrings[i] = context.getValueFactories().getStringFactory().create(expectedValues[i]);
+        }
+        assertThat(actualValuesAsStrings, is(expectedValuesAsStrings));
+    }
+
+    protected void assertNodeHasNoProperty( String path,
+                                            String propertyName ) {
+        Location parent = Location.create(path(path));
+        ReadAllPropertiesRequest request = new ReadAllPropertiesRequest(parent, "fedSpace");
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path(path)));
+        assertThat(request.getPropertiesByName().containsKey(name(propertyName)), is(false));
+    }
+
     @Test( expected = IllegalArgumentException.class )
     public void shouldFailWhenExecutionContextIsNull() {
         context = null;
@@ -568,9 +652,203 @@
         assertThat(contributions.get(2).isExpired(nowPlus310InUtc), is(true)); // expired by cache @ 219
     }
 
+    /**
+     * Set up the sources such that:
+     * <ul>
+     * <li>Source 1: "{@code /a => /source/one/a}", "{@code /b => /source/one/b}"</li>
+     * <li>Source 2: "{@code /a => /source/two/a}"</li>
+     * <li>Source 1: "{@code / => /}"</li>
+     * </ul>
+     */
+    protected void initializeSourcesWithContent() {
+        // Set up the content of source 1
+        Graph repository1 = Graph.create(source1, context);
+        repository1.createWorkspace().named("workspace1");
+        Graph.Batch batch = repository1.batch();
+        batch.create("/source").and();
+        batch.create("/source/one").and();
+        batch.create("/source/one/a").with("desc", "source 1 node a description").and();
+        batch.create("/source/one/a/nA").with("desc", "source 1 node nA description").and();
+        batch.create("/source/one/a/nB").with("desc", "source 1 node nB description").and();
+        batch.create("/source/one/a/nC").with("desc", "source 1 node nC description").and();
+        batch.create("/source/one/b").with("desc", "source 1 node b description").and();
+        batch.create("/source/one/b/pA").with("desc", "source 1 node pA description").and();
+        batch.create("/source/one/b/pB").with("desc", "source 1 node pB description").and();
+        batch.create("/source/one/b/pC").with("desc", "source 1 node pC description").and();
+        batch.execute();
+
+        // Set up the content of source 2
+        Graph repository2 = Graph.create(source2, context);
+        repository2.createWorkspace().named("workspace2");
+        batch = repository2.batch();
+        batch.create("/source").and();
+        batch.create("/source/two").and();
+        batch.create("/source/two/a").with("desc", "source 2 node a description").and();
+        batch.create("/source/two/a/qA").with("desc", "source 2 node qA description").and();
+        batch.create("/source/two/a/qB").with("desc", "source 2 node qB description").and();
+        batch.create("/source/two/a/qC").with("desc", "source 2 node qC description").and();
+        batch.execute();
+
+        // Set up the content of source 3
+        Graph repository3 = Graph.create(source3, context);
+        repository3.createWorkspace().named("workspace3");
+        batch = repository3.batch();
+        batch.create("/x").and();
+        batch.create("/x/y").with("desc", "y description").and();
+        batch.create("/x/y/zA").with("desc", "zA description").and();
+        batch.create("/x/y/zB").with("desc", "zB description").and();
+        batch.create("/x/y/zC").with("desc", "zC description").and();
+        batch.create("/b").and();
+        batch.create("/b/by").with("desc", "by description").and();
+        batch.create("/b/by/bzA").with("desc", "bzA description").and();
+        batch.create("/b/by/bzB").with("desc", "bzB description").and();
+        batch.create("/a").and();
+        batch.create("/a/ay").with("desc", "ay description").and();
+        batch.create("/a/ay/azA").with("desc", "azA description").and();
+        batch.create("/a/ay/azB").with("desc", "azB description").and();
+        batch.execute();
+
+    }
+
+    @Test( expected = UnsupportedRequestException.class )
+    public void shouldNotCreateNodeIfParentIsOutsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location parent = Location.create(path("/a")); // source 2 or 3?
+        CreateNodeRequest request = new CreateNodeRequest(parent, "fedSpace", name("child"));
+        executor.process(request);
+        if (request.hasError()) throw request.getError();
+    }
+
     @Test
-    public void shouldGetNodeUsingPath() {
+    public void shouldCreateNodeOnlyIfParentIsInsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location parent = Location.create(path("/a/ay")); // source 3
+        CreateNodeRequest request = new CreateNodeRequest(parent, "fedSpace", name("child"));
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path("/a/ay/child")));
+        assertNodeHasChildren("/a/ay", "azA", "azB", "child");
+    }
 
+    @Test( expected = UnsupportedRequestException.class )
+    public void shouldNotDestroyNodeIfOutsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location location = Location.create(path("/a")); // source 2 or 3?
+        DeleteBranchRequest request = new DeleteBranchRequest(location, "fedSpace");
+        executor.process(request);
+        if (request.hasError()) throw request.getError();
     }
 
+    @Test
+    public void shouldDestroyNodeOnlyIfInsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        Location location = Location.create(path("/a/ay")); // source 3
+        DeleteBranchRequest request = new DeleteBranchRequest(location, "fedSpace");
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path("/a/ay")));
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC");
+    }
+
+    @Test( expected = UnsupportedRequestException.class )
+    public void shouldNotUpdateNodeIfOutsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location location = Location.create(path("/a")); // source 2 or 3?
+        Map<Name, Property> properties = new HashMap<Name, Property>();
+        addProperty(properties, "prop1", "value1");
+        addProperty(properties, "prop2", "value2a", "value2b");
+        UpdatePropertiesRequest request = new UpdatePropertiesRequest(location, "fedSpace", properties);
+        executor.process(request);
+        if (request.hasError()) throw request.getError();
+    }
+
+    @Test
+    public void shouldUpdateNodeOnlyIfInsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        // assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasProperty("/a/ay", "desc", "ay description");
+        assertNodeHasNoProperty("/a/ay", "prop1");
+        assertNodeHasNoProperty("/a/ay", "prop2");
+        Location location = Location.create(path("/a/ay")); // source 3
+        Map<Name, Property> properties = new HashMap<Name, Property>();
+        addProperty(properties, "desc", "ay description 2");
+        addProperty(properties, "prop1", "value1");
+        addProperty(properties, "prop2", "value2a", "value2b");
+        UpdatePropertiesRequest request = new UpdatePropertiesRequest(location, "fedSpace", properties);
+        executor.process(request);
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationOfNode().getPath(), is(path("/a/ay")));
+        assertNodeHasProperty("/a/ay", "desc", "ay description 2");
+        assertNodeHasProperty("/a/ay", "prop1", "value1");
+        assertNodeHasProperty("/a/ay", "prop2", "value2a", "value2b");
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+    }
+
+    @Test( expected = UnsupportedRequestException.class )
+    public void shouldNotMoveNodeIfParentIsOutsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location from = Location.create(path("/a/ay")); // source 3
+        Location into = Location.create(path("/b")); // source 1 or source 3?
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasChildren("/b", "pA", "pB", "pC", "by");
+        MoveBranchRequest request = new MoveBranchRequest(from, into, "fedSpace");
+        executor.process(request);
+        if (request.hasError()) throw request.getError();
+    }
+
+    @Test
+    public void shouldMoveNodeOnlyIfParentIsInsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location from = Location.create(path("/a/ay")); // source 3
+        Location into = Location.create(path("/b/by")); // source 3
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasChildren("/b", "pA", "pB", "pC", "by");
+        assertNodeHasChildren("/b/by", "bzA", "bzB");
+        MoveBranchRequest request = new MoveBranchRequest(from, into, "fedSpace");
+        executor.process(request);
+        if (request.hasError()) {
+            System.out.println(request.getError().getMessage());
+        }
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationBefore().getPath(), is(path("/a/ay")));
+        assertThat(request.getActualLocationAfter().getPath(), is(path("/b/by/ay")));
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC");
+        assertNodeHasChildren("/b/by", "bzA", "bzB", "ay");
+    }
+
+    @Test( expected = UnsupportedRequestException.class )
+    public void shouldNotCopyNodeIfParentIsOutsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location from = Location.create(path("/a/ay")); // source 3
+        Location into = Location.create(path("/b")); // source 1 or source 3?
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasChildren("/b", "pA", "pB", "pC", "by");
+        CopyBranchRequest request = new CopyBranchRequest(from, "fedSpace", into, "fedSpace");
+        executor.process(request);
+        if (request.hasError()) throw request.getError();
+    }
+
+    @Test
+    public void shouldCopyNodeOnlyIfParentIsInsideSingleProjection() throws Throwable {
+        initializeSourcesWithContent();
+        Location from = Location.create(path("/a/ay")); // source 3
+        Location into = Location.create(path("/b/by")); // source 3
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasChildren("/a/ay", "azA", "azB");
+        assertNodeHasChildren("/b", "pA", "pB", "pC", "by");
+        assertNodeHasChildren("/b/by", "bzA", "bzB");
+        CopyBranchRequest request = new CopyBranchRequest(from, "fedSpace", into, "fedSpace");
+        executor.process(request);
+        if (request.hasError()) {
+            System.out.println(request.getError().getMessage());
+        }
+        assertThat(request.hasError(), is(false));
+        assertThat(request.getActualLocationBefore().getPath(), is(path("/a/ay")));
+        assertThat(request.getActualLocationAfter().getPath(), is(path("/b/by/ay")));
+        assertNodeHasChildren("/a", "nA", "nB", "nC", "qA", "qB", "qC", "ay");
+        assertNodeHasChildren("/b/by", "bzA", "bzB", "ay");
+        assertNodeHasChildren("/b/by/ay", "azA", "azB");
+    }
+
 }

Modified: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategyTest.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategyTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/OneContributionMergeStrategyTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -104,6 +104,7 @@
         stub(contribution.getProperties()).toReturn(properties.values().iterator());
         strategy.merge(node, contributions, context);
         properties.put(DnaLexicon.UUID, node.getPropertiesByName().get(DnaLexicon.UUID));
+        properties.put(DnaLexicon.MERGE_PLAN, node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN));
         assertThat(node.getPropertiesByName(), is(properties));
     }
 

Modified: trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategyTest.java
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategyTest.java	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/strategy/SimpleMergeStrategyTest.java	2009-04-30 04:32:26 UTC (rev 866)
@@ -88,8 +88,9 @@
     public void shouldCombinePropertiesFromOneContribution() {
         addContribution("source1").setProperty("p1", "p1 value");
         strategy.merge(node, contributions, context);
-        assertThat(node.getProperties().size(), is(2));
+        assertThat(node.getProperties().size(), is(3));
         assertThat(node.getPropertiesByName().get(DnaLexicon.UUID), is(notNullValue()));
+        assertThat(node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN), is(notNullValue()));
         assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
     }
 
@@ -122,8 +123,9 @@
         addContribution("source1").setProperty("p1", "p1 value");
         addContribution("source2").setProperty("p2", "p2 value");
         strategy.merge(node, contributions, context);
-        assertThat(node.getProperties().size(), is(3));
+        assertThat(node.getProperties().size(), is(4));
         assertThat(node.getPropertiesByName().get(DnaLexicon.UUID), is(notNullValue()));
+        assertThat(node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN), is(notNullValue()));
         assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
         assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
     }
@@ -133,8 +135,9 @@
         addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
         addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", "3", "4");
         strategy.merge(node, contributions, context);
-        assertThat(node.getProperties().size(), is(4));
+        assertThat(node.getProperties().size(), is(5));
         assertThat(node.getPropertiesByName().get(DnaLexicon.UUID), is(notNullValue()));
+        assertThat(node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN), is(notNullValue()));
         assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
         assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
         assertThat(node.getPropertiesByName().get(name("p12")), is(property("p12", "1", "2", "3", "4")));
@@ -145,7 +148,8 @@
         addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
         addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", "3", "4");
         strategy.merge(node, contributions, context);
-        assertThat(node.getProperties().size(), is(4));
+        assertThat(node.getProperties().size(), is(5));
+        assertThat(node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN), is(notNullValue()));
         assertThat(node.getPropertiesByName().get(DnaLexicon.UUID), is(notNullValue()));
         for (Contribution contribution : contributions) {
             Iterator<Property> iter = contribution.getProperties();
@@ -173,8 +177,9 @@
         addContribution("source1").setProperty("p1", "p1 value").setProperty("p12", "1", "2", "3");
         addContribution("source2").setProperty("p2", "p2 value").setProperty("p12", 3, 4);
         strategy.merge(node, contributions, context);
-        assertThat(node.getProperties().size(), is(4));
+        assertThat(node.getProperties().size(), is(5));
         assertThat(node.getPropertiesByName().get(DnaLexicon.UUID), is(notNullValue()));
+        assertThat(node.getPropertiesByName().get(DnaLexicon.MERGE_PLAN), is(notNullValue()));
         assertThat(node.getPropertiesByName().get(name("p1")), is(property("p1", "p1 value")));
         assertThat(node.getPropertiesByName().get(name("p2")), is(property("p2", "p2 value")));
         assertThat(node.getPropertiesByName().get(name("p12")), is(property("p12", "1", "2", "3", 4)));

Modified: trunk/extensions/dna-connector-federation/src/test/resources/log4j.properties
===================================================================
--- trunk/extensions/dna-connector-federation/src/test/resources/log4j.properties	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/extensions/dna-connector-federation/src/test/resources/log4j.properties	2009-04-30 04:32:26 UTC (rev 866)
@@ -9,7 +9,7 @@
 
 # Set up the default logging to be INFO level, then override specific units
 log4j.logger.org.jboss.dna=INFO
-log4j.logger.org.jboss.dna.connector.federation.executor=INFO
+log4j.logger.org.jboss.dna.connector.federation=TRACE
 
 # Jackrabbit logging
 log4j.logger.org.apache.jackrabbit=WARN, stdout

Modified: trunk/pom.xml
===================================================================
--- trunk/pom.xml	2009-04-29 14:31:49 UTC (rev 865)
+++ trunk/pom.xml	2009-04-30 04:32:26 UTC (rev 866)
@@ -135,7 +135,7 @@
 		<module>extensions/dna-common-jdbc</module> 
 		<module>extensions/dna-connector-jdbc-metadata</module> 
 		<module>dna-integration-tests</module>
-		<!--module>docs/examples/gettingstarted</module-->
+		<module>docs/examples/gettingstarted</module>
 	</modules>
 
 	<profiles>




More information about the dna-commits mailing list