[dna-commits] DNA SVN: r369 - in trunk: connectors and 39 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Thu Jul 24 15:52:14 EDT 2008


Author: rhauch
Date: 2008-07-24 15:52:13 -0400 (Thu, 24 Jul 2008)
New Revision: 369

Added:
   trunk/connectors/dna-connector-federation/
   trunk/connectors/dna-connector-federation/.classpath
   trunk/connectors/dna-connector-federation/.project
   trunk/connectors/dna-connector-federation/pom.xml
   trunk/connectors/dna-connector-federation/src/
   trunk/connectors/dna-connector-federation/src/main/
   trunk/connectors/dna-connector-federation/src/main/java/
   trunk/connectors/dna-connector-federation/src/main/java/org/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepository.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConfig.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConnection.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositorySource.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationException.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/Projection.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/ProjectionParser.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/AbstractContribution.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/Contribution.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/EmptyContribution.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/NodeContribution.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/PropertyContribution.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ActsOnProjectedPathCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyBranchCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyNodeCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCreateNodeCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedDeleteBranchCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetChildrenCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetNodeCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetPropertiesCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedMoveBranchCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedRecordBranchCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedSetPropertiesCommand.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/SingleProjectionCommandExecutor.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicFederatedNode.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicMergePlan.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/DoubleContributionMergePlan.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/FederatedNode.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MultipleContributionMergePlan.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SingleContributionMergePlan.java
   trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/TripleContributionMergePlan.java
   trunk/connectors/dna-connector-federation/src/main/resources/
   trunk/connectors/dna-connector-federation/src/main/resources/org/
   trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/
   trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/
   trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/
   trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/
   trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties
   trunk/connectors/dna-connector-federation/src/test/
   trunk/connectors/dna-connector-federation/src/test/java/
   trunk/connectors/dna-connector-federation/src/test/java/org/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryConnectionTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositorySourceTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederationI18nTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionParserTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionPathRuleTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionTest.java
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/
   trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/BasicFederatedNodeTest.java
   trunk/connectors/dna-connector-federation/src/test/resources/
   trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryService.java
   trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositorySourceManager.java
   trunk/dna-repository/src/test/java/org/jboss/dna/repository/RepositoryServiceTest.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/ExecutionContextFactory.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/DelegatingCommandExecutor.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/LoggingCommandExecutor.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/NoOpCommandExecutor.java
   trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepository.java
   trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepositorySource.java
Removed:
   trunk/dna-repository/src/main/java/org/jboss/dna/repository/federation/
   trunk/dna-repository/src/test/java/org/jboss/dna/repository/federation/
Modified:
   trunk/dna-repository/pom.xml
   trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
   trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Path.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/PathFactory.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutor.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/CommandExecutor.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCompositeCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCopyNodeCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCreateNodeCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicDeleteBranchCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetChildrenCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetNodeCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetPropertiesCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicMoveBranchCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicRecordBranchCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicSetPropertiesCommand.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/BasicPath.java
   trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/PathValueFactory.java
   trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties
   trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutorTest.java
   trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/BasicPathTest.java
   trunk/pom.xml
Log:
DNA-115 - Moved the federating functionality into a separate connector and out of 'dna-repository'.  This cleans up the 'dna-repository' project and also makes it easier to assemble and (re)use the federation capability in different parts of the system.


Property changes on: trunk/connectors/dna-connector-federation
___________________________________________________________________
Name: svn:ignore
   + target


Added: trunk/connectors/dna-connector-federation/.classpath
===================================================================
--- trunk/connectors/dna-connector-federation/.classpath	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/.classpath	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+    <classpathentry kind="src" path="src/main/java"/>
+    <classpathentry kind="src" path="src/main/resources"/>
+    <classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+    <classpathentry kind="src" output="target/test-classes" path="src/test/resources"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

Added: trunk/connectors/dna-connector-federation/.project
===================================================================
--- trunk/connectors/dna-connector-federation/.project	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/.project	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>dna-connector-federation</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.maven.ide.eclipse.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.maven.ide.eclipse.maven2Nature</nature>
+	</natures>
+</projectDescription>

Added: trunk/connectors/dna-connector-federation/pom.xml
===================================================================
--- trunk/connectors/dna-connector-federation/pom.xml	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/pom.xml	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.jboss.dna</groupId>
+    <artifactId>dna</artifactId>
+    <version>0.2-SNAPSHOT</version>
+    <relativePath>../..</relativePath>
+  </parent>
+  <!-- The groupId and version values are inherited from parent -->
+  <artifactId>dna-connector-federation</artifactId>
+  <packaging>jar</packaging>
+  <name>JBoss DNA Federation Connector</name>
+  <description>JBoss DNA Connector that federates content from multiple connectors.</description>
+  <url>http://labs.jboss.org/dna</url>
+  
+  <properties>
+    <dna-version>0.2-SNAPSHOT</dna-version>
+  </properties>
+  <!--
+  Define the dependencies.  Note that all version and scopes default to those 
+  defined in the dependencyManagement section of the parent pom.
+  -->
+  <dependencies>
+    <!-- 
+    Common
+    -->
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-spi</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-common</artifactId>
+      <version>${dna-version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-spi</artifactId>
+      <version>${dna-version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+    <!-- 
+    Testing (note the scope)
+    -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+    </dependency>
+    <!-- 
+    Logging (require SLF4J API for compiling, but use Log4J and its SLF4J binding for testing) 
+    -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+    </dependency>
+    <!-- 
+    Java Concurrency in Practice annotations
+    -->
+  <dependency>
+    <groupId>net.jcip</groupId>
+    <artifactId>jcip-annotations</artifactId>
+  </dependency>
+  </dependencies>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
\ No newline at end of file

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepository.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepository.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepository.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,259 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.connector.federation.executor.FederatingCommandExecutor;
+import org.jboss.dna.spi.graph.commands.executor.CommandExecutor;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceListener;
+
+/**
+ * The component that represents a single federated repository. The federated repository uses a set of {@link RepositorySource
+ * federated sources} as designated by name through the {@link #getConfiguration() configuration}, and provides the logic of
+ * interacting with those sources and presenting a single unified graph.
+ * 
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class FederatedRepository {
+
+    private final ExecutionEnvironment env;
+    private final RepositoryConnectionFactories connectionFactories;
+    private FederatedRepositoryConfig config;
+    private final AtomicInteger openExecutors = new AtomicInteger(0);
+    private final CountDownLatch shutdownLatch = new CountDownLatch(1);
+    private final AtomicBoolean shutdownRequested = new AtomicBoolean(false);
+    private final CopyOnWriteArrayList<RepositorySourceListener> listeners = new CopyOnWriteArrayList<RepositorySourceListener>();
+
+    /**
+     * Create a federated repository instance.
+     * 
+     * @param env the execution environment
+     * @param connectionFactories the set of connection factories that should be used
+     * @param config the configuration for this repository
+     * @throws IllegalArgumentException if any of the parameters are null, or if the name is blank
+     */
+    public FederatedRepository( ExecutionEnvironment env,
+                                RepositoryConnectionFactories connectionFactories,
+                                FederatedRepositoryConfig config ) {
+        ArgCheck.isNotNull(connectionFactories, "connectionFactories");
+        ArgCheck.isNotNull(env, "env");
+        ArgCheck.isNotNull(config, "config");
+        this.env = env;
+        this.connectionFactories = connectionFactories;
+        this.config = config;
+    }
+
+    /**
+     * Get the name of this repository
+     * 
+     * @return name
+     */
+    public String getName() {
+        return this.config.getName();
+    }
+
+    /**
+     * @return the execution environment
+     */
+    public ExecutionEnvironment getExecutionEnvironment() {
+        return env;
+    }
+
+    /**
+     * @return connectionFactories
+     */
+    protected RepositoryConnectionFactories getConnectionFactories() {
+        return connectionFactories;
+    }
+
+    /**
+     * Utility method called by the administrator.
+     */
+    public synchronized void start() {
+        // Do not establish connections to the sources; these will be established as needed
+    }
+
+    /**
+     * Return true if this federated repository is running and ready for connections.
+     * 
+     * @return true if running, or false otherwise
+     */
+    public boolean isRunning() {
+        return this.shutdownRequested.get() != true;
+    }
+
+    /**
+     * Utility method called by the administrator.
+     */
+    public synchronized void shutdown() {
+        this.shutdownRequested.set(true);
+        if (this.openExecutors.get() <= 0) shutdownLatch.countDown();
+    }
+
+    /**
+     * Utility method called by the administrator.
+     * 
+     * @param timeout
+     * @param unit
+     * @return true if all connections open at the time this method is called were {@link RepositoryConnection#close() closed} in
+     *         the supplied time, or false if the timeout occurred before all the connections were closed
+     * @throws InterruptedException
+     */
+    public boolean awaitTermination( long timeout,
+                                     TimeUnit unit ) throws InterruptedException {
+        // Await until all connections have been closed, or until the timeout occurs
+        return shutdownLatch.await(timeout, unit);
+    }
+
+    /**
+     * Return true if this federated repository has completed its termination and no longer has any open connections.
+     * 
+     * @return true if terminated, or false otherwise
+     */
+    public boolean isTerminated() {
+        return this.openExecutors.get() != 0;
+    }
+
+    /**
+     * Add a listener that is to receive notifications to changes to content within this repository. This method does nothing if
+     * the supplied listener is null.
+     * 
+     * @param listener the new listener
+     * @return true if the listener was added, or false if the listener was not added (if reference is null, or if non-null
+     *         listener is already an existing listener)
+     */
+    public boolean addListener( RepositorySourceListener listener ) {
+        if (listener == null) return false;
+        return this.listeners.addIfAbsent(listener);
+    }
+
+    /**
+     * Remove the supplied listener. This method does nothing if the supplied listener is null.
+     * <p>
+     * This method can safely be called while the federation repository is in use.
+     * </p>
+     * 
+     * @param listener the listener to remove
+     * @return true if the listener was removed, or false if the listener was not registered
+     */
+    public boolean removeListener( RepositorySourceListener listener ) {
+        if (listener == null) return false;
+        return this.listeners.remove(listener);
+    }
+
+    /**
+     * Get the list of listeners, which is the actual list used by the repository.
+     * 
+     * @return the listeners
+     */
+    public List<RepositorySourceListener> getListeners() {
+        return this.listeners;
+    }
+
+    /**
+     * Authenticate the supplied username with the supplied credentials, and return whether authentication was successful.
+     * 
+     * @param source the {@link RepositorySource} that should be affiliated with the resulting connection
+     * @param username the username
+     * @param credentials the credentials
+     * @return the repository connection if authentication succeeded, or null otherwise
+     */
+    public RepositoryConnection createConnection( RepositorySource source,
+                                                  String username,
+                                                  Object credentials ) {
+        return new FederatedRepositoryConnection(this, source.getName());
+    }
+
+    /**
+     * Get the configuration of this repository. This configuration is immutable and may be
+     * {@link #setConfiguration(FederatedRepositoryConfig) changed} as needed. Therefore, when using a configuration and needing a
+     * consistent configuration, maintain a reference to the configuration during that time (as the actual configuration may be
+     * replaced at any time).
+     * 
+     * @return the repository's configuration at the time this method is called.
+     */
+    public FederatedRepositoryConfig getConfiguration() {
+        return config;
+    }
+
+    /**
+     * Set the configuration for this repository. The configuration is immutable and therefore may be replaced using this method.
+     * All interaction with the configuration is done in a thread-safe and concurrent manner, and as such only valid
+     * configurations should be used.
+     * 
+     * @param config the new configuration
+     * @throws IllegalArgumentException if the configuration is null
+     */
+    public void setConfiguration( FederatedRepositoryConfig config ) {
+        ArgCheck.isNotNull(config, "config");
+        this.config = config;
+    }
+
+    /**
+     * Called by
+     * {@link FederatedRepositoryConnection#execute(ExecutionEnvironment, org.jboss.dna.spi.graph.commands.GraphCommand...)}.
+     * 
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @return the executor
+     */
+    protected CommandExecutor getExecutor( ExecutionEnvironment env,
+                                           String sourceName ) {
+        FederatedRepositoryConfig config = this.getConfiguration();
+        return new FederatingCommandExecutor(env, sourceName, config.getCacheProjection(), config.getSourceProjections(),
+                                             getConnectionFactories());
+    }
+
+    /**
+     * Called by {@link FederatedRepositoryConnection#FederatedRepositoryConnection(FederatedRepository, String)}.
+     * 
+     * @param connection the connection being opened
+     */
+    /*package*/void register( FederatedRepositoryConnection connection ) {
+        openExecutors.incrementAndGet();
+    }
+
+    /**
+     * Called by {@link FederatedRepositoryConnection#close()}.
+     * 
+     * @param connection the connection being closed
+     */
+    /*package*/void unregister( FederatedRepositoryConnection connection ) {
+        if (openExecutors.decrementAndGet() <= 0 && shutdownRequested.get()) {
+            // Last connection, so turn out the lights ...
+            shutdownLatch.countDown();
+        }
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConfig.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConfig.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConfig.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,177 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.ThreadSafeProblems;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+
+/**
+ * The configuration of a federated repository. The configuration defines, among other things, the set of
+ * {@link #getSourceProjections() source projections} in the federated repository that each specify how and where content from a
+ * {@link RepositorySource source} is federated into the unified repository.
+ * 
+ * @author Randall Hauch
+ */
+ at Immutable
+public class FederatedRepositoryConfig implements Comparable<FederatedRepositoryConfig> {
+
+    private final Projection cacheProjection;
+    private final List<Projection> sourceProjections;
+    private final Problems problems;
+    private final String name;
+    private final CachePolicy defaultCachePolicy;
+
+    /**
+     * Create a federated repository instance.
+     * 
+     * @param repositoryName the name of the repository
+     * @param cacheProjection the projection used for the cache; may not be null
+     * @param sourceProjections the source projections; may not be null
+     * @param defaultCachePolicy the default cache policy for this repository; may be null
+     * @throws IllegalArgumentException if the name is null or is blank
+     */
+    public FederatedRepositoryConfig( String repositoryName,
+                                      Projection cacheProjection,
+                                      Iterable<Projection> sourceProjections,
+                                      CachePolicy defaultCachePolicy ) {
+        ArgCheck.isNotEmpty(repositoryName, "repositoryName");
+        ArgCheck.isNotNull(cacheProjection, "cacheProjection");
+        this.name = repositoryName;
+        this.problems = new ThreadSafeProblems();
+        this.defaultCachePolicy = defaultCachePolicy;
+        this.cacheProjection = cacheProjection;
+        List<Projection> projectionList = new ArrayList<Projection>();
+        for (Projection projection : sourceProjections) {
+            if (projection == null) continue;
+            if (!projectionList.contains(projection)) {
+                projectionList.add(projection);
+            }
+        }
+        this.sourceProjections = Collections.unmodifiableList(projectionList);
+        ArgCheck.isNotEmpty(this.sourceProjections, "sourceProjections");
+    }
+
+    /**
+     * Get the name of this repository
+     * 
+     * @return name
+     */
+    public String getName() {
+        return this.name;
+    }
+
+    /**
+     * Return the problem associated with this configuration. These problems may change at any time, although the returned
+     * {@link Problems} object is thread-safe.
+     * 
+     * @return the thread-safe problems for this configuration
+     */
+    public Problems getProblems() {
+        return problems;
+    }
+
+    /**
+     * Get the projection that defines the cache for this repository. This projection does not exist in the
+     * {@link #getSourceProjections() list of source projections}.
+     * 
+     * @return the region used for caching; never null
+     */
+    public Projection getCacheProjection() {
+        return cacheProjection;
+    }
+
+    /**
+     * Return the unmodifiable list of source projections.
+     * 
+     * @return the source projections; never null and never empty
+     */
+    public List<Projection> getSourceProjections() {
+        return sourceProjections;
+    }
+
+    /**
+     * Get the default cache policy for the repository with the supplied name
+     * 
+     * @return the default cache policy
+     */
+    public CachePolicy getDefaultCachePolicy() {
+        return defaultCachePolicy;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return this.name.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        if (obj instanceof FederatedRepositoryConfig) {
+            FederatedRepositoryConfig that = (FederatedRepositoryConfig)obj;
+            if (!this.getName().equals(that.getName())) return false;
+            if (!this.getCacheProjection().equals(that.getCacheProjection())) return false;
+            if (!this.getSourceProjections().equals(that.getSourceProjections())) return false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo( FederatedRepositoryConfig that ) {
+        if (that == this) return 0;
+        int diff = this.getName().compareTo(that.getName());
+        if (diff != 0) return diff;
+        diff = this.getCacheProjection().compareTo(that.getCacheProjection());
+        if (diff != 0) return diff;
+        Iterator<Projection> thisIter = this.getSourceProjections().iterator();
+        Iterator<Projection> thatIter = that.getSourceProjections().iterator();
+        while (thisIter.hasNext() && thatIter.hasNext()) {
+            diff = thisIter.next().compareTo(thatIter.next());
+            if (diff != 0) return diff;
+        }
+        if (thisIter.hasNext()) return 1;
+        if (thatIter.hasNext()) return -1;
+        return 0;
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConnection.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConnection.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositoryConnection.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,139 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.transaction.xa.XAResource;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.executor.CommandExecutor;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+import org.jboss.dna.spi.graph.connection.RepositorySourceListener;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class FederatedRepositoryConnection implements RepositoryConnection {
+
+    protected static final RepositorySourceListener NO_OP_LISTENER = new RepositorySourceListener() {
+        public void notify( String sourceName,
+                            Object... events ) {
+            // do nothing
+        }
+    };
+
+    private final FederatedRepository repository;
+    private final String sourceName;
+    private final AtomicReference<RepositorySourceListener> listener;
+
+    protected FederatedRepositoryConnection( FederatedRepository repository,
+                                             String sourceName ) {
+        assert sourceName != null;
+        assert repository != null;
+        this.sourceName = sourceName;
+        this.repository = repository;
+        this.listener = new AtomicReference<RepositorySourceListener>(NO_OP_LISTENER);
+        this.repository.register(this);
+    }
+
+    /**
+     * @return repository
+     */
+    protected FederatedRepository getRepository() {
+        return this.repository;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getSourceName() {
+        return this.sourceName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public CachePolicy getDefaultCachePolicy() {
+        return this.repository.getConfiguration().getDefaultCachePolicy();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public XAResource getXAResource() {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setListener( RepositorySourceListener listener ) {
+        if (listener == null) listener = NO_OP_LISTENER;
+        RepositorySourceListener oldListener = this.listener.getAndSet(listener);
+        this.repository.addListener(listener);
+        if (oldListener != NO_OP_LISTENER) {
+            this.repository.removeListener(oldListener);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean ping( long time,
+                         TimeUnit unit ) {
+        return this.repository.isRunning();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void execute( ExecutionEnvironment env,
+                         GraphCommand... commands ) throws RepositorySourceException, InterruptedException {
+        if (!this.repository.isRunning()) {
+            throw new RepositorySourceException(FederationI18n.repositoryHasBeenShutDown.text(this.repository.getName()));
+        }
+        if (commands == null || commands.length == 0) return;
+
+        CommandExecutor executor = this.repository.getExecutor(env, sourceName);
+        assert executor != null;
+        for (GraphCommand command : commands) {
+            executor.execute(command);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() {
+        try {
+            this.repository.removeListener(this.listener.get());
+        } finally {
+            this.repository.unregister(this);
+        }
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositorySource.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositorySource.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederatedRepositorySource.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,961 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.StringRefAddr;
+import javax.naming.spi.ObjectFactory;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.LoginException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.SimpleProblems;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.connector.federation.executor.FederatingCommandExecutor;
+import org.jboss.dna.connector.federation.executor.SingleProjectionCommandExecutor;
+import org.jboss.dna.spi.ExecutionContextFactory;
+import org.jboss.dna.spi.cache.BasicCachePolicy;
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.InvalidPathException;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.NameFactory;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.ValueFactories;
+import org.jboss.dna.spi.graph.ValueFactory;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.executor.CommandExecutor;
+import org.jboss.dna.spi.graph.commands.executor.LoggingCommandExecutor;
+import org.jboss.dna.spi.graph.commands.executor.NoOpCommandExecutor;
+import org.jboss.dna.spi.graph.commands.impl.BasicCompositeCommand;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetNodeCommand;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class FederatedRepositorySource implements RepositorySource {
+
+    /**
+     */
+    private static final long serialVersionUID = 7587346948013486977L;
+
+    public static final int DEFAULT_RETRY_LIMIT = 0;
+    public static final String[] DEFAULT_CONFIGURATION_SOURCE_PROJECTION_RULES = {"/dna:system => /"};
+
+    protected static final String REPOSITORY_NAME = "repositoryName";
+    protected static final String SOURCE_NAME = "sourceName";
+    protected static final String USERNAME = "username";
+    protected static final String PASSWORD = "password";
+    protected static final String CONFIGURATION_SOURCE_NAME = "configurationSourceName";
+    protected static final String CONFIGURATION_SOURCE_PROJECTION_RULES = "configurationSourceProjectionRules";
+    protected static final String CONNECTION_FACTORIES_JNDI_NAME = "connectionFactoriesJndiName";
+    protected static final String EXECUTION_CONTEXT_FACTORY_JNDI_NAME = "executionContextFacotryJndiName";
+    protected static final String REPOSITORY_JNDI_NAME = "repositoryJndiName";
+    protected static final String SECURITY_DOMAIN = "securityDomain";
+    protected static final String RETRY_LIMIT = "retryLimit";
+
+    protected static final String PROJECTION_RULES_CONFIG_PROPERTY_NAME = "dna:projectionRules";
+    protected static final String CACHE_POLICY_TIME_TO_EXPIRE_CONFIG_PROPERTY_NAME = "dna:timeToExpire";
+    protected static final String CACHE_POLICY_TIME_TO_CACHE_CONFIG_PROPERTY_NAME = "dna:timeToCache";
+
+    private String repositoryName;
+    private String sourceName;
+    private String username;
+    private String password;
+    private String configurationSourceName;
+    private String[] configurationSourceProjectionRules = DEFAULT_CONFIGURATION_SOURCE_PROJECTION_RULES;
+    private String connectionFactoriesJndiName;
+    private String executionContextFactoryJndiName;
+    private String securityDomain;
+    private String repositoryJndiName;
+    private int retryLimit = DEFAULT_RETRY_LIMIT;
+    private transient FederatedRepository repository;
+    private transient Context jndiContext;
+
+    /**
+     * Create a new instance of the source, which must still be properly initialized with a {@link #setRepositoryName(String)
+     * repository name}.
+     */
+    public FederatedRepositorySource() {
+    }
+
+    /**
+     * Create a new instance of the source with the required repository name and federation service.
+     * 
+     * @param repositoryName the repository name
+     * @throws IllegalArgumentException if the federation service is null or the repository name is null or blank
+     */
+    public FederatedRepositorySource( String repositoryName ) {
+        ArgCheck.isNotNull(repositoryName, "repositoryName");
+        this.repositoryName = repositoryName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getRetryLimit() {
+        return this.retryLimit;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setRetryLimit( int limit ) {
+        this.retryLimit = limit > 0 ? limit : 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized RepositoryConnection getConnection() throws RepositorySourceException {
+        if (getName() == null) {
+            throw new RepositorySourceException(FederationI18n.propertyIsRequired.text("name"));
+        }
+        if (getExecutionContextFactoryJndiName() == null) {
+            throw new RepositorySourceException(FederationI18n.propertyIsRequired.text("execution context factory JNDI name"));
+        }
+        if (getSecurityDomain() == null) {
+            throw new RepositorySourceException(FederationI18n.propertyIsRequired.text("security domain"));
+        }
+        if (getConnectionFactoriesJndiName() == null) {
+            throw new RepositorySourceException(FederationI18n.propertyIsRequired.text("connection factories JNDI name"));
+        }
+        // Find the repository ...
+        FederatedRepository repository = getRepository();
+        // Authenticate the user ...
+        String username = this.username;
+        Object credentials = this.password;
+        RepositoryConnection connection = repository.createConnection(this, username, credentials);
+        if (connection == null) {
+            I18n msg = FederationI18n.unableToAuthenticateConnectionToFederatedRepository;
+            throw new RepositorySourceException(msg.text(this.repositoryName, username));
+        }
+        // Return the new connection ...
+        return connection;
+    }
+
+    /**
+     * Get the {@link FederatedRepository} instance that this source is using. This method uses the following logic:
+     * <ol>
+     * <li>If a {@link FederatedRepository} already was obtained from a prior call, the same instance is returned.</li>
+     * <li>A {@link FederatedRepository} is created using a {@link FederatedRepositoryConfig} is created from this instance's
+     * properties and {@link ExecutionEnvironment} and {@link RepositoryConnectionFactories} instances obtained from JNDI.</li>
+     * <li></li>
+     * <li></li>
+     * </ol>
+     * 
+     * @return the federated repository instance
+     * @throws RepositorySourceException
+     */
+    protected synchronized FederatedRepository getRepository() throws RepositorySourceException {
+        if (repository == null) {
+            String jndiName = this.getRepositoryJndiName();
+            Context context = getContext();
+            if (jndiName != null && jndiName.trim().length() != 0) {
+                // Look for an existing repository in JNDI ...
+                try {
+                    if (context == null) context = new InitialContext();
+                    repository = (FederatedRepository)context.lookup(jndiName);
+                } catch (Throwable err) {
+                    I18n msg = FederationI18n.unableToFindFederatedRepositoryInJndi;
+                    throw new RepositorySourceException(msg.text(this.sourceName, jndiName), err);
+                }
+            }
+
+            if (repository == null) {
+                // Find in JNDI the repository connection factories and the environment ...
+                ExecutionEnvironment env = getExecutionEnvironment();
+                RepositoryConnectionFactories factories = getRepositoryConnectionFactories();
+                // And create the configuration and the repository ...
+                FederatedRepositoryConfig config = getRepositoryConfiguration(env, factories);
+                repository = new FederatedRepository(env, factories, config);
+            }
+        }
+        return repository;
+    }
+
+    protected ExecutionEnvironment getExecutionEnvironment() {
+        ExecutionContextFactory factory = null;
+        Context context = getContext();
+        String jndiName = getExecutionContextFactoryJndiName();
+        if (jndiName != null && jndiName.trim().length() != 0) {
+            try {
+                if (context == null) context = new InitialContext();
+                factory = (ExecutionContextFactory)context.lookup(jndiName);
+            } catch (Throwable err) {
+                I18n msg = FederationI18n.unableToFindExecutionContextFactoryInJndi;
+                throw new RepositorySourceException(msg.text(this.sourceName, jndiName), err);
+            }
+        }
+        if (factory == null) {
+            I18n msg = FederationI18n.unableToFindExecutionContextFactoryInJndi;
+            throw new RepositorySourceException(msg.text(this.sourceName, jndiName));
+        }
+        String securityDomain = getSecurityDomain();
+        CallbackHandler handler = createCallbackHandler();
+        try {
+            return factory.create(securityDomain, handler);
+        } catch (LoginException e) {
+            I18n msg = FederationI18n.unableToCreateExecutionContext;
+            throw new RepositorySourceException(msg.text(this.sourceName, jndiName, securityDomain), e);
+        }
+    }
+
+    protected RepositoryConnectionFactories getRepositoryConnectionFactories() {
+        RepositoryConnectionFactories factories = null;
+        Context context = getContext();
+        String jndiName = getConnectionFactoriesJndiName();
+        if (jndiName != null && jndiName.trim().length() != 0) {
+            try {
+                if (context == null) context = new InitialContext();
+                factories = (RepositoryConnectionFactories)context.lookup(jndiName);
+            } catch (Throwable err) {
+                I18n msg = FederationI18n.unableToFindRepositoryConnectionFactoriesInJndi;
+                throw new RepositorySourceException(msg.text(this.sourceName, jndiName), err);
+            }
+        }
+        if (factories == null) {
+            I18n msg = FederationI18n.noRepositoryConnectionFactories;
+            throw new RepositorySourceException(msg.text(this.repositoryName));
+        }
+        return factories;
+    }
+
+    protected CallbackHandler createCallbackHandler() {
+        return new CallbackHandler() {
+            public void handle( Callback[] callbacks ) {
+                for (Callback callback : callbacks) {
+                    if (callback instanceof NameCallback) {
+                        NameCallback nameCallback = (NameCallback)callback;
+                        nameCallback.setName(FederatedRepositorySource.this.getUsername());
+                    }
+                    if (callback instanceof PasswordCallback) {
+                        PasswordCallback passwordCallback = (PasswordCallback)callback;
+                        passwordCallback.setPassword(FederatedRepositorySource.this.getPassword().toCharArray());
+                    }
+                }
+            }
+        };
+    }
+
+    protected Context getContext() {
+        return this.jndiContext;
+    }
+
+    protected synchronized void setContext( Context context ) {
+        this.jndiContext = context;
+    }
+
+    /**
+     * Create a {@link FederatedRepositoryConfig} instance from the current properties of this instance. This method does
+     * <i>not</i> modify the state of this instance.
+     * 
+     * @param env the execution environment that should be used to read the configuration; may not be null
+     * @param factories the factories from which can be obtained the RepositoryConnectionFactory instances for each name source;
+     *        may not be null
+     * @return a configuration reflecting the current state of this instance
+     */
+    protected synchronized FederatedRepositoryConfig getRepositoryConfiguration( ExecutionEnvironment env,
+                                                                                 RepositoryConnectionFactories factories ) {
+        Problems problems = new SimpleProblems();
+        ValueFactories valueFactories = env.getValueFactories();
+        PathFactory pathFactory = valueFactories.getPathFactory();
+        NameFactory nameFactory = valueFactories.getNameFactory();
+        ValueFactory<Long> longFactory = valueFactories.getLongFactory();
+
+        // Create the configuration projection ...
+        ProjectionParser projectionParser = ProjectionParser.getInstance();
+        Projection.Rule[] rules = projectionParser.rulesFromStrings(env, this.getConfigurationSourceProjectionRules());
+        Projection configurationProjection = new Projection(this.getConfigurationSourceName(), rules);
+
+        // Create a federating command executor to execute the commands and merge the results into a single set of
+        // commands.
+        final String configurationSourceName = configurationProjection.getSourceName();
+        List<Projection> projections = Collections.singletonList(configurationProjection);
+        CommandExecutor executor = null;
+        if (configurationProjection.getRules().size() == 1) {
+            // There is just a single projection for the configuration repository, so just use an executor that
+            // translates the paths using the projection
+            executor = new SingleProjectionCommandExecutor(env, configurationSourceName, configurationProjection, factories);
+        } else if (configurationProjection.getRules().size() == 0) {
+            // There is no projection for the configuration repository, so just use a no-op executor
+            executor = new NoOpCommandExecutor(env, configurationSourceName);
+        } else {
+            // The configuration repository has more than one projection, so we need to merge the results
+            executor = new FederatingCommandExecutor(env, configurationSourceName, null, projections, factories);
+        }
+        // Wrap the executor with a logging executor ...
+        executor = new LoggingCommandExecutor(executor, Logger.getLogger(getClass()), Logger.Level.INFO);
+
+        // The configuration projection (via "executor") will convert this path into a path that exists in the configuration
+        // repository
+        Path configNode = pathFactory.create("/dna:system/dna:federation");
+
+        try {
+            // Get the repository node ...
+            BasicGetNodeCommand getRepository = new BasicGetNodeCommand(configNode);
+            executor.execute(getRepository);
+            if (getRepository.hasError()) {
+                throw new FederationException(FederationI18n.federatedRepositoryCannotBeFound.text(repositoryName));
+            }
+
+            // Add a command to get the projection defining the cache ...
+            Path pathToCacheRegion = pathFactory.create(configNode, nameFactory.create("dna:cache"));
+            BasicGetNodeCommand getCacheRegion = new BasicGetNodeCommand(pathToCacheRegion);
+            executor.execute(getCacheRegion);
+            Projection cacheProjection = createProjection(env,
+                                                          projectionParser,
+                                                          getCacheRegion.getPath(),
+                                                          getCacheRegion.getProperties(),
+                                                          problems);
+
+            if (getCacheRegion.hasError()) {
+                I18n msg = FederationI18n.requiredNodeDoesNotExistRelativeToNode;
+                throw new FederationException(msg.text("dna:cache", configNode));
+            }
+
+            // Get the source projections for the repository ...
+            Path projectionsNode = pathFactory.create(configNode, nameFactory.create("dna:projections"));
+            BasicGetChildrenCommand getProjections = new BasicGetChildrenCommand(projectionsNode);
+
+            executor.execute(getProjections);
+            if (getProjections.hasError()) {
+                I18n msg = FederationI18n.requiredNodeDoesNotExistRelativeToNode;
+                throw new FederationException(msg.text("dna:projections", configNode));
+            }
+
+            // Build the commands to get each of the projections (children of the "dna:projections" node) ...
+            List<Projection> sourceProjections = new LinkedList<Projection>();
+            if (getProjections.hasNoError() && !getProjections.getChildren().isEmpty()) {
+                BasicCompositeCommand commands = new BasicCompositeCommand();
+                for (Path.Segment child : getProjections.getChildren()) {
+                    final Path pathToSource = pathFactory.create(projectionsNode, child);
+                    commands.add(new BasicGetNodeCommand(pathToSource));
+                }
+                // Now execute these commands ...
+                executor.execute(commands);
+
+                // Iterate over each region node obtained ...
+                for (GraphCommand command : commands) {
+                    BasicGetNodeCommand getProjectionCommand = (BasicGetNodeCommand)command;
+                    if (getProjectionCommand.hasNoError()) {
+                        Projection projection = createProjection(env,
+                                                                 projectionParser,
+                                                                 getProjectionCommand.getPath(),
+                                                                 getProjectionCommand.getProperties(),
+                                                                 problems);
+                        if (projection != null) sourceProjections.add(projection);
+                    }
+                }
+            }
+
+            // Look for the default cache policy ...
+            BasicCachePolicy cachePolicy = new BasicCachePolicy();
+            Property timeToExpireProperty = getRepository.getProperties().get(nameFactory.create(CACHE_POLICY_TIME_TO_EXPIRE_CONFIG_PROPERTY_NAME));
+            Property timeToCacheProperty = getRepository.getProperties().get(nameFactory.create(CACHE_POLICY_TIME_TO_CACHE_CONFIG_PROPERTY_NAME));
+            if (timeToCacheProperty != null && !timeToCacheProperty.isEmpty()) {
+                cachePolicy.setTimeToCache(longFactory.create(timeToCacheProperty.getValues().next()));
+            }
+            if (timeToExpireProperty != null && !timeToExpireProperty.isEmpty()) {
+                cachePolicy.setTimeToExpire(longFactory.create(timeToExpireProperty.getValues().next()));
+            }
+            CachePolicy defaultCachePolicy = cachePolicy.isEmpty() ? null : cachePolicy.getUnmodifiable();
+            return new FederatedRepositoryConfig(repositoryName, cacheProjection, sourceProjections, defaultCachePolicy);
+        } catch (InvalidPathException err) {
+            I18n msg = FederationI18n.federatedRepositoryCannotBeFound;
+            throw new FederationException(msg.text(repositoryName));
+        } catch (InterruptedException err) {
+            I18n msg = FederationI18n.interruptedWhileUsingFederationConfigurationRepository;
+            throw new FederationException(msg.text(repositoryName));
+        }
+
+    }
+
+    /**
+     * Instantiate the {@link Projection} described by the supplied properties.
+     * 
+     * @param env the execution environment that should be used to read the configuration; may not be null
+     * @param projectionParser the projection rule parser that should be used; may not be null
+     * @param path the path to the node where these properties were found; never null
+     * @param properties the properties; never null
+     * @param problems the problems container in which any problems should be reported; never null
+     * @return the region instance, or null if it could not be created
+     */
+    protected Projection createProjection( ExecutionEnvironment env,
+                                           ProjectionParser projectionParser,
+                                           Path path,
+                                           Map<Name, Property> properties,
+                                           Problems problems ) {
+        ValueFactories valueFactories = env.getValueFactories();
+        NameFactory nameFactory = valueFactories.getNameFactory();
+        ValueFactory<String> stringFactory = valueFactories.getStringFactory();
+
+        String sourceName = path.getLastSegment().getName().getLocalName();
+
+        // Get the rules ...
+        Projection.Rule[] projectionRules = null;
+        Property projectionRulesProperty = properties.get(nameFactory.create(PROJECTION_RULES_CONFIG_PROPERTY_NAME));
+        if (projectionRulesProperty != null && !projectionRulesProperty.isEmpty()) {
+            String[] projectionRuleStrs = stringFactory.create(projectionRulesProperty.getValuesAsArray());
+            if (projectionRuleStrs != null && projectionRuleStrs.length != 0) {
+                projectionRules = projectionParser.rulesFromStrings(env, projectionRuleStrs);
+            }
+        }
+        if (problems.hasErrors()) return null;
+
+        Projection region = new Projection(sourceName, projectionRules);
+        return region;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized String getName() {
+        return sourceName;
+    }
+
+    /**
+     * Set the name of this source.
+     * <p>
+     * This is a required property.
+     * </p>
+     * 
+     * @param sourceName the name of this repository source
+     * @see #setConfigurationSourceName(String)
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setName(String)
+     */
+    public synchronized void setName( String sourceName ) {
+        if (this.sourceName == sourceName || this.sourceName != null && this.sourceName.equals(sourceName)) return;
+        this.sourceName = sourceName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * Get the name in JNDI of a {@link FederatedRepository} instance that should be used. If this is set (and an instance can be
+     * found at that location), few of the remaining properties on this instance may not be used (basically just
+     * {@link #getUsername() username}, {@link #getPassword() password}, and {@link #getName() source name}).
+     * <p>
+     * This is an optional property.
+     * </p>
+     * 
+     * @return the location in JNDI of the {@link FederatedRepository} that should be used by this source, or null if the
+     *         {@link FederatedRepository} instance will be created from the properties of this instance
+     * @see #setRepositoryJndiName(String)
+     */
+    public String getRepositoryJndiName() {
+        return repositoryJndiName;
+    }
+
+    /**
+     * Set the name in JNDI of a {@link FederatedRepository} instance that should be used. If this is set (and an instance can be
+     * found at that location), few of the remaining properties on this instance may not be used (basically just
+     * {@link #getUsername() username}, {@link #getPassword() password}, and {@link #getName() source name}).
+     * <p>
+     * This is an optional property.
+     * </p>
+     * 
+     * @param jndiName the JNDI name where the {@link FederatedRepository} instance can be found, or null if the instance is not
+     *        to be found in JNDI but one should be instantiated from this instance's properties
+     * @see #getRepositoryJndiName()
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setName(String)
+     */
+    public void setRepositoryJndiName( String jndiName ) {
+        if (this.repositoryJndiName == jndiName || this.repositoryJndiName != null && this.repositoryJndiName.equals(jndiName)) return;
+        this.repositoryJndiName = jndiName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * Get the name in JNDI of a {@link RepositorySource} instance that should be used by the {@link FederatedRepository federated
+     * repository} as the configuration repository.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @return the JNDI name of the {@link RepositorySource} instance that should be used for the configuration, or null if the
+     *         federated repository instance is to be found in JNDI
+     * @see #setConfigurationSourceName(String)
+     */
+    public String getConfigurationSourceName() {
+        return configurationSourceName;
+    }
+
+    /**
+     * Get the name of a {@link RepositorySource} instance that should be used by the {@link FederatedRepository federated
+     * repository} as the configuration repository. The instance will be retrieved from the {@link RepositoryConnectionFactories}
+     * instance {@link #getConnectionFactoriesJndiName() found in JDNI}.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @param sourceName the name of the {@link RepositorySource} instance that should be used for the configuration, or null if
+     *        the federated repository instance is to be found in JNDI
+     * @see #getConfigurationSourceName()
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public void setConfigurationSourceName( String sourceName ) {
+        if (this.configurationSourceName == sourceName || this.configurationSourceName != null
+            && this.configurationSourceName.equals(sourceName)) return;
+        this.configurationSourceName = sourceName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * Get the projection rule definitions used for the {@link #getConfigurationSourceName() configuration source}. The
+     * {@link #DEFAULT_CONFIGURATION_SOURCE_PROJECTION_RULES default projection rules} map the root of the configuration source
+     * into the <code>/dna:system</code> branch of the repository.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @return the string array of projection rules, or null if the projection rules haven't yet been set or if the federated
+     *         repository instance is to be found in JNDI
+     * @see #setConfigurationSourceProjectionRules(String[])
+     */
+    public String[] getConfigurationSourceProjectionRules() {
+        return configurationSourceProjectionRules;
+    }
+
+    /**
+     * Get the projection rule definitions used for the {@link #getConfigurationSourceName() configuration source}. The
+     * {@link #DEFAULT_CONFIGURATION_SOURCE_PROJECTION_RULES default projection rules} map the root of the configuration source
+     * into the <code>/dna:system</code> branch of the repository.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @param projectionRules the string array of projection rules, or null if the projection rules haven't yet been set or if the
+     *        federated repository instance is to be found in JNDI
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setConfigurationSourceName(String)
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public void setConfigurationSourceProjectionRules( String[] projectionRules ) {
+        if (projectionRules != null) {
+            List<String> rules = new LinkedList<String>();
+            for (String rule : projectionRules) {
+                if (rule != null && rule.trim().length() != 0) rules.add(rule);
+            }
+            projectionRules = rules.toArray(new String[rules.size()]);
+        }
+        this.configurationSourceProjectionRules = projectionRules != null ? projectionRules : DEFAULT_CONFIGURATION_SOURCE_PROJECTION_RULES;
+    }
+
+    /**
+     * Get the name in JNDI of a {@link ExecutionContextFactory} instance that should be used to obtain the
+     * {@link ExecutionEnvironment execution context} used by the {@link FederatedRepository federated repository}.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @return the JNDI name of the {@link ExecutionContextFactory} instance that should be used, or null if the federated
+     *         repository instance is to be found in JNDI
+     * @see #setExecutionContextFactoryJndiName(String)
+     */
+    public String getExecutionContextFactoryJndiName() {
+        return executionContextFactoryJndiName;
+    }
+
+    /**
+     * Set the name in JNDI of a {@link ExecutionContextFactory} instance that should be used to obtain the
+     * {@link ExecutionEnvironment execution context} used by the {@link FederatedRepository federated repository}.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @param jndiName the JNDI name where the {@link ExecutionContextFactory} instance can be found, or null if the federated
+     *        repository instance is to be found in JNDI
+     * @see #getExecutionContextFactoryJndiName()
+     * @see #setConfigurationSourceName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setName(String)
+     */
+    public synchronized void setExecutionContextFactoryJndiName( String jndiName ) {
+        if (this.repositoryJndiName == jndiName || this.repositoryJndiName != null && this.repositoryJndiName.equals(jndiName)) return;
+        this.executionContextFactoryJndiName = jndiName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * Get the name in JNDI where the {@link RepositoryConnectionFactories} instance that can be used by the
+     * {@link FederatedRepository federated repository} can find any {@link RepositorySource} sources it needs, including those
+     * used for {@link Projection sources} and that used for it's {@link #getConfigurationSourceName() configuration}.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @return the JNDI name where the {@link RepositoryConnectionFactories} instance can be found, or null if the federated
+     *         repository instance is to be found in JNDI
+     * @see #setConnectionFactoriesJndiName(String)
+     */
+    public String getConnectionFactoriesJndiName() {
+        return connectionFactoriesJndiName;
+    }
+
+    /**
+     * Set the name in JNDI where the {@link RepositoryConnectionFactories} instance that can be used by the
+     * {@link FederatedRepository federated repository} can find any {@link RepositorySource} sources it needs, including those
+     * used for {@link Projection sources} and that used for it's {@link #getConfigurationSourceName() configuration}.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @param jndiName the JNDI name where the {@link RepositoryConnectionFactories} instance can be found, or null if the
+     *        federated repository instance is to be found in JNDI
+     * @see #getConnectionFactoriesJndiName()
+     * @see #setConfigurationSourceName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public synchronized void setConnectionFactoriesJndiName( String jndiName ) {
+        if (this.connectionFactoriesJndiName == jndiName || this.connectionFactoriesJndiName != null
+            && this.connectionFactoriesJndiName.equals(jndiName)) return;
+        this.connectionFactoriesJndiName = jndiName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * @return securityDomain
+     */
+    public String getSecurityDomain() {
+        return securityDomain;
+    }
+
+    /**
+     * @param securityDomain Sets securityDomain to the specified value.
+     */
+    public void setSecurityDomain( String securityDomain ) {
+        this.securityDomain = securityDomain;
+    }
+
+    /**
+     * Get the name of the federated repository.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @return the name of the repository
+     * @see #setRepositoryName(String)
+     */
+    public synchronized String getRepositoryName() {
+        return this.repositoryName;
+    }
+
+    /**
+     * Get the name of the federated repository.
+     * <p>
+     * This is a required property (unless the {@link #getRepositoryJndiName() federated repository is to be found in JDNI}).
+     * </p>
+     * 
+     * @param repositoryName the new name of the repository
+     * @throws IllegalArgumentException if the repository name is null, empty or blank
+     * @see #getRepositoryName()
+     * @see #setConfigurationSourceName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setPassword(String)
+     * @see #setUsername(String)
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public synchronized void setRepositoryName( String repositoryName ) {
+        ArgCheck.isNotEmpty(repositoryName, "repositoryName");
+        if (this.repositoryName != null && this.repositoryName.equals(repositoryName)) return;
+        this.repositoryName = repositoryName;
+        changeRepositoryConfig();
+    }
+
+    /**
+     * This method is called to signal that some aspect of the configuration has changed. If a {@link #getRepository() repository}
+     * instance has been created, it's configuration is
+     * {@link #getRepositoryConfiguration(ExecutionEnvironment, RepositoryConnectionFactories) rebuilt} and updated. Nothing is
+     * done, however, if there is currently no {@link #getRepository() repository}.
+     */
+    protected synchronized void changeRepositoryConfig() {
+        if (this.repository != null) {
+            // Find in JNDI the repository connection factories and the environment ...
+            ExecutionEnvironment env = getExecutionEnvironment();
+            RepositoryConnectionFactories factories = getRepositoryConnectionFactories();
+            // Compute a new repository config and set it on the repository ...
+            FederatedRepositoryConfig newConfig = getRepositoryConfiguration(env, factories);
+            this.repository.setConfiguration(newConfig);
+        }
+    }
+
+    /**
+     * Get the username that should be used when authenticating and {@link #getConnection() creating connections}.
+     * <p>
+     * This is an optional property, required only when authentication is to be used.
+     * </p>
+     * 
+     * @return the username, or null if no username has been set or are not to be used
+     * @see #setUsername(String)
+     */
+    public String getUsername() {
+        return this.username;
+    }
+
+    /**
+     * Set the username that should be used when authenticating and {@link #getConnection() creating connections}.
+     * <p>
+     * This is an optional property, required only when authentication is to be used.
+     * </p>
+     * 
+     * @param username the username, or null if no username has been set or are not to be used
+     * @see #getUsername()
+     * @see #setPassword(String)
+     * @see #setConfigurationSourceName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setPassword(String)
+     * @see #setRepositoryName(String)
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public void setUsername( String username ) {
+        this.username = username;
+    }
+
+    /**
+     * Get the password that should be used when authenticating and {@link #getConnection() creating connections}.
+     * <p>
+     * This is an optional property, required only when authentication is to be used.
+     * </p>
+     * 
+     * @return the password, or null if no password have been set or are not to be used
+     * @see #setPassword(String)
+     */
+    public String getPassword() {
+        return this.password;
+    }
+
+    /**
+     * Get the password that should be used when authenticating and {@link #getConnection() creating connections}.
+     * <p>
+     * This is an optional property, required only when authentication is to be used.
+     * </p>
+     * 
+     * @param password the password, or null if no password have been set or are not to be used
+     * @see #getPassword()
+     * @see #setConfigurationSourceName(String)
+     * @see #setConfigurationSourceProjectionRules(String[])
+     * @see #setUsername(String)
+     * @see #setRepositoryName(String)
+     * @see #setConnectionFactoriesJndiName(String)
+     * @see #setExecutionContextFactoryJndiName(String)
+     * @see #setName(String)
+     */
+    public void setPassword( String password ) {
+        this.password = password;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized Reference getReference() {
+        String className = getClass().getName();
+        String factoryClassName = NamingContextObjectFactory.class.getName();
+        Reference ref = new Reference(className, factoryClassName, null);
+
+        if (getRepositoryName() != null) {
+            ref.add(new StringRefAddr(REPOSITORY_NAME, getRepositoryName()));
+        }
+        if (getName() != null) {
+            ref.add(new StringRefAddr(SOURCE_NAME, getName()));
+        }
+        if (getUsername() != null) {
+            ref.add(new StringRefAddr(USERNAME, getUsername()));
+        }
+        if (getPassword() != null) {
+            ref.add(new StringRefAddr(PASSWORD, getPassword()));
+        }
+        if (getConfigurationSourceName() != null) {
+            ref.add(new StringRefAddr(CONFIGURATION_SOURCE_NAME, getConfigurationSourceName()));
+        }
+        if (getConfigurationSourceProjectionRules() != null) {
+            StringBuilder sb = new StringBuilder();
+            boolean first = true;
+            for (String rule : getConfigurationSourceProjectionRules()) {
+                if (!first) {
+                    sb.append("\n");
+                    first = false;
+                }
+                sb.append(rule);
+            }
+            ref.add(new StringRefAddr(CONFIGURATION_SOURCE_PROJECTION_RULES, sb.toString()));
+        }
+        if (getConnectionFactoriesJndiName() != null) {
+            ref.add(new StringRefAddr(CONNECTION_FACTORIES_JNDI_NAME, getConnectionFactoriesJndiName()));
+        }
+        if (getExecutionContextFactoryJndiName() != null) {
+            ref.add(new StringRefAddr(EXECUTION_CONTEXT_FACTORY_JNDI_NAME, getExecutionContextFactoryJndiName()));
+        }
+        if (getSecurityDomain() != null) {
+            ref.add(new StringRefAddr(SECURITY_DOMAIN, getSecurityDomain()));
+        }
+        if (getRepositoryJndiName() != null) {
+            ref.add(new StringRefAddr(REPOSITORY_JNDI_NAME, getRepositoryJndiName()));
+        }
+        ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
+        return ref;
+    }
+
+    public static class NamingContextObjectFactory implements ObjectFactory {
+
+        public NamingContextObjectFactory() {
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Object getObjectInstance( Object obj,
+                                         javax.naming.Name name,
+                                         Context nameCtx,
+                                         Hashtable<?, ?> environment ) throws Exception {
+            if (obj instanceof Reference) {
+                Map<String, String> values = new HashMap<String, String>();
+                Reference ref = (Reference)obj;
+                Enumeration<?> en = ref.getAll();
+                while (en.hasMoreElements()) {
+                    RefAddr subref = (RefAddr)en.nextElement();
+                    if (subref instanceof StringRefAddr) {
+                        String key = subref.getType();
+                        Object value = subref.getContent();
+                        if (value != null) values.put(key, value.toString());
+                    }
+                }
+                String repositoryName = values.get(FederatedRepositorySource.REPOSITORY_NAME);
+                String sourceName = values.get(FederatedRepositorySource.SOURCE_NAME);
+                String username = values.get(FederatedRepositorySource.USERNAME);
+                String password = values.get(FederatedRepositorySource.PASSWORD);
+                String configurationSourceName = values.get(FederatedRepositorySource.CONFIGURATION_SOURCE_NAME);
+                String projectionRules = values.get(FederatedRepositorySource.CONFIGURATION_SOURCE_PROJECTION_RULES);
+                String connectionFactoriesJndiName = values.get(FederatedRepositorySource.CONNECTION_FACTORIES_JNDI_NAME);
+                String environmentJndiName = values.get(FederatedRepositorySource.EXECUTION_CONTEXT_FACTORY_JNDI_NAME);
+                String repositoryJndiName = values.get(FederatedRepositorySource.REPOSITORY_JNDI_NAME);
+                String securityDomain = values.get(FederatedRepositorySource.SECURITY_DOMAIN);
+                String retryLimit = values.get(FederatedRepositorySource.RETRY_LIMIT);
+
+                // Create the source instance ...
+                FederatedRepositorySource source = new FederatedRepositorySource();
+                if (repositoryName != null) source.setRepositoryName(repositoryName);
+                if (sourceName != null) source.setName(sourceName);
+                if (username != null) source.setUsername(username);
+                if (password != null) source.setPassword(password);
+                if (configurationSourceName != null) source.setConfigurationSourceName(configurationSourceName);
+                if (projectionRules != null) {
+                    List<String> rules = StringUtil.splitLines(projectionRules);
+                    source.setConfigurationSourceProjectionRules(rules.toArray(new String[rules.size()]));
+                }
+                if (connectionFactoriesJndiName != null) source.setConnectionFactoriesJndiName(connectionFactoriesJndiName);
+                if (environmentJndiName != null) source.setExecutionContextFactoryJndiName(environmentJndiName);
+                if (repositoryJndiName != null) source.setRepositoryJndiName(repositoryJndiName);
+                if (securityDomain != null) source.setSecurityDomain(securityDomain);
+                if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
+                return source;
+            }
+            return null;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return repositoryName.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        if (obj instanceof FederatedRepositorySource) {
+            FederatedRepositorySource that = (FederatedRepositorySource)obj;
+            // The repository name, source name, and federation service must all match
+            if (!this.getRepositoryName().equals(that.getRepositoryName())) return false;
+            if (this.getName() == null) {
+                if (that.getName() != null) return false;
+            } else {
+                if (!this.getName().equals(that.getName())) return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationException.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationException.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationException.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+
+/**
+ * 
+ * @author Randall Hauch
+ */
+public class FederationException extends RuntimeException {
+
+    /**
+     */
+    private static final long serialVersionUID = -27536888064529864L;
+
+    /**
+     * 
+     */
+    public FederationException() {
+    }
+
+    /**
+     * @param message
+     */
+    public FederationException( String message ) {
+        super(message);
+
+    }
+
+    /**
+     * @param cause
+     */
+    public FederationException( Throwable cause ) {
+        super(cause);
+
+    }
+
+    /**
+     * @param message
+     * @param cause
+     */
+    public FederationException( String message, Throwable cause ) {
+        super(message, cause);
+
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/FederationI18n.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,70 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.util.Locale;
+import java.util.Set;
+import org.jboss.dna.common.i18n.I18n;
+
+/**
+ * @author Randall Hauch
+ */
+public final class FederationI18n {
+
+    public static I18n requiredNodeDoesNotExistRelativeToNode;
+    public static I18n propertyIsRequired;
+
+    public static I18n interruptedWhileUsingFederationConfigurationRepository;
+    public static I18n unableToFindFederatedRepositoryInJndi;
+    public static I18n unableToFindExecutionContextFactoryInJndi;
+    public static I18n unableToCreateExecutionContext;
+    public static I18n unableToFindRepositoryConnectionFactoriesInJndi;
+    public static I18n noRepositoryConnectionFactories;
+    public static I18n federatedRepositoryCannotBeFound;
+    public static I18n unableToFindRepositorySourceByName;
+    public static I18n unableToCreateConnectionToFederatedRepository;
+    public static I18n unableToAuthenticateConnectionToFederatedRepository;
+    public static I18n repositoryHasBeenShutDown;
+    public static I18n repositoryPathInFederationBindingIsNotAbsolute;
+    public static I18n errorReadingMergePlan;
+    public static I18n errorAddingProjectionRuleParseMethod;
+
+    static {
+        try {
+            I18n.initialize(FederationI18n.class);
+        } catch (final Exception err) {
+            System.err.println(err);
+        }
+    }
+
+    public static Set<Locale> getLocalizationProblemLocales() {
+        return I18n.getLocalizationProblemLocales(FederationI18n.class);
+    }
+
+    public static Set<String> getLocalizationProblems() {
+        return I18n.getLocalizationProblems(FederationI18n.class);
+    }
+
+    public static Set<String> getLocalizationProblems( Locale locale ) {
+        return I18n.getLocalizationProblems(FederationI18n.class, locale);
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/Projection.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/Projection.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/Projection.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,734 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.spi.graph.NamespaceRegistry;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+
+/**
+ * A projection of content from a source into the integrated/federated repository. Each project consists of a set of {@link Rule
+ * rules} for a particular source, where each rule defines how content within a source is
+ * {@link Rule#getPathInRepository(Path, PathFactory) is project into the repository} and how the repository content is
+ * {@link Rule#getPathInSource(Path, PathFactory) projected into the source}. Different rule subclasses are used for different
+ * types.
+ * 
+ * @author Randall Hauch
+ */
+ at Immutable
+public class Projection implements Comparable<Projection>, Serializable {
+
+    /**
+     * Initial version
+     */
+    private static final long serialVersionUID = 1L;
+    protected static final List<Method> parserMethods;
+    static {
+        parserMethods = new CopyOnWriteArrayList<Method>();
+        try {
+            parserMethods.add(Projection.class.getDeclaredMethod("parsePathRule", String.class, ExecutionEnvironment.class));
+        } catch (Throwable err) {
+            Logger.getLogger(Projection.class).error(err, FederationI18n.errorAddingProjectionRuleParseMethod);
+        }
+    }
+
+    /**
+     * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
+     * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
+     * {@link ExecutionEnvironment} environment reference as the second parameter, and should return the resulting {@link Rule}
+     * (or null if the definition format could not be understood by the method. Any exceptions during
+     * {@link Method#invoke(Object, Object...) invocation} will be logged at the
+     * {@link Logger#trace(Throwable, String, Object...) trace} level.
+     * 
+     * @param method the method to be added
+     * @see #addRuleParser(ClassLoader, String, String)
+     */
+    public static void addRuleParser( Method method ) {
+        if (method != null) parserMethods.add(method);
+    }
+
+    /**
+     * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
+     * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
+     * {@link ExecutionEnvironment} environment reference as the second parameter, and should return the resulting {@link Rule}
+     * (or null if the definition format could not be understood by the method. Any exceptions during
+     * {@link Method#invoke(Object, Object...) invocation} will be logged at the
+     * {@link Logger#trace(Throwable, String, Object...) trace} level.
+     * 
+     * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null
+     * @param className the name of the class on which the static method is defined; may not be null
+     * @param methodName the name of the method
+     * @throws SecurityException if there is a security exception while loading the class or getting the method
+     * @throws NoSuchMethodException if the method does not exist on the class
+     * @throws ClassNotFoundException if the class could not be found given the supplied class loader
+     * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
+     *         empty
+     * @see #addRuleParser(Method)
+     */
+    public static void addRuleParser( ClassLoader classLoader,
+                                      String className,
+                                      String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
+        ArgCheck.isNotNull(classLoader, "classLoader");
+        ArgCheck.isNotEmpty(className, "className");
+        ArgCheck.isNotEmpty(methodName, "methodName");
+        Class<?> clazz = Class.forName(className, true, classLoader);
+        parserMethods.add(clazz.getMethod(className, String.class, ExecutionEnvironment.class));
+    }
+
+    /**
+     * Remove the rule parser method.
+     * 
+     * @param method the method to remove
+     * @return true if the method was removed, or false if the method was not a registered rule parser method
+     */
+    public static boolean removeRuleParser( Method method ) {
+        return parserMethods.remove(method);
+    }
+
+    /**
+     * Remove the rule parser method.
+     * 
+     * @param declaringClassName the name of the class on which the static method is defined; may not be null
+     * @param methodName the name of the method
+     * @return true if the method was removed, or false if the method was not a registered rule parser method
+     * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
+     *         empty
+     */
+    public static boolean removeRuleParser( String declaringClassName,
+                                            String methodName ) {
+        ArgCheck.isNotEmpty(declaringClassName, "declaringClassName");
+        ArgCheck.isNotEmpty(methodName, "methodName");
+        for (Method method : parserMethods) {
+            if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
+                return parserMethods.remove(method);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Parse the string form of a rule definition and return the rule
+     * 
+     * @param definition the definition of the rule that is to be parsed
+     * @param env the environment in which this method is being executed; may not be null
+     * @return the rule, or null if the definition could not be parsed
+     */
+    public static Rule fromString( String definition,
+                                   ExecutionEnvironment env ) {
+        ArgCheck.isNotNull(env, "env");
+        definition = definition != null ? definition.trim() : "";
+        if (definition.length() == 0) return null;
+        for (Method method : parserMethods) {
+            try {
+                Rule rule = (Rule)method.invoke(null, definition, env);
+                if (rule != null) return rule;
+            } catch (Throwable err) {
+                String msg = "Error while parsing project rule definition \"{0}\" using {1}";
+                Logger.getLogger(Projection.class).trace(err, msg, definition, method);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Pattern that identifies the form:
+     * 
+     * <pre>
+     *    repository_path [=&gt; source_path [$ exception ]*]?
+     * </pre>
+     * 
+     * where the following groups are captured on the first call to {@link Matcher#find()}:
+     * <ol>
+     * <li><code>repository_path</code></li>
+     * <li><code>source_path</code></li>
+     * </ol>
+     * and the following groups are captured on subsequent calls to {@link Matcher#find()}:
+     * <ol>
+     * <li>exception</code></li>
+     * </ol>
+     * <p>
+     * The regular expression is:
+     * 
+     * <pre>
+     * ((?:[&circ;=$]|=(?!&gt;))+)(?:(?:=&gt;((?:[&circ;=$]|=(?!&gt;))+))( \$ (?:(?:[&circ;=]|=(?!&gt;))+))*)?
+     * </pre>
+     * 
+     * </p>
+     */
+    protected static final String PATH_RULE_PATTERN_STRING = "((?:[^=$]|=(?!>))+)(?:(?:=>((?:[^=$]|=(?!>))+))( \\$ (?:(?:[^=]|=(?!>))+))*)?";
+    protected static final Pattern PATH_RULE_PATTERN = Pattern.compile(PATH_RULE_PATTERN_STRING);
+
+    /**
+     * Parse the string definition of a {@link PathRule}. This method is automatically registered in the {@link #parserMethods
+     * parser methods} by the static initializer of {@link Projection}.
+     * 
+     * @param definition the definition
+     * @param env the environment
+     * @return the path rule, or null if the definition is not in the right form
+     */
+    public static PathRule parsePathRule( String definition,
+                                          ExecutionEnvironment env ) {
+        definition = definition != null ? definition.trim() : "";
+        if (definition.length() == 0) return null;
+        Matcher matcher = PATH_RULE_PATTERN.matcher(definition);
+        if (!matcher.find()) return null;
+        String reposPathStr = matcher.group(1);
+        String sourcePathStr = matcher.group(2);
+        PathFactory pathFactory = env.getValueFactories().getPathFactory();
+        Path repositoryPath = pathFactory.create(reposPathStr);
+        Path sourcePath = pathFactory.create(sourcePathStr);
+
+        // Grab the exceptions ...
+        List<Path> exceptions = new LinkedList<Path>();
+        while (matcher.find()) {
+            String exceptionStr = matcher.group(1);
+            Path exception = pathFactory.create(exceptionStr);
+            exceptions.add(exception);
+        }
+        return new PathRule(repositoryPath, sourcePath, exceptions);
+    }
+
+    private final String sourceName;
+    private final List<Rule> rules;
+
+    /**
+     * Create a new federated projection for the supplied source, using the supplied rules.
+     * 
+     * @param sourceName the name of the source
+     * @param rules the projection rules
+     * @throws IllegalArgumentException if the source name or rule array is null, empty, or contains all nulls
+     */
+    public Projection( String sourceName,
+                       Rule... rules ) {
+        ArgCheck.isNotEmpty(sourceName, "sourceName");
+        ArgCheck.isNotEmpty(rules, "rules");
+        this.sourceName = sourceName;
+        List<Rule> rulesList = new ArrayList<Rule>();
+        for (Rule rule : rules) {
+            if (rule != null) rulesList.add(rule);
+        }
+        this.rules = Collections.unmodifiableList(rulesList);
+        ArgCheck.isNotEmpty(this.rules, "rules");
+    }
+
+    /**
+     * Get the name of the source to which this projection applies.
+     * 
+     * @return the source name
+     * @see RepositorySource#getName()
+     */
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    /**
+     * Get the rules that define this projection.
+     * 
+     * @return the unmodifiable list of immutable rules; never null
+     */
+    public List<Rule> getRules() {
+        return rules;
+    }
+
+    /**
+     * Get the paths in the source that correspond to the supplied path within the repository. This method computes the paths
+     * given all of the rules. In general, most sources will probably project a node onto a single repository node. However, some
+     * sources may be configured such that the same node in the repository is a projection of multiple nodes within the source.
+     * 
+     * @param canonicalPathInRepository the canonical path of the node within the repository; may not be null
+     * @param factory the path factory; may not be null
+     * @return the set of unique paths in the source projected from the repository path; never null
+     * @throws IllegalArgumentException if the factory reference is null
+     */
+    public Set<Path> getPathsInSource( Path canonicalPathInRepository,
+                                       PathFactory factory ) {
+        ArgCheck.isNotNull(factory, "factory");
+        assert canonicalPathInRepository == null ? true : canonicalPathInRepository.equals(canonicalPathInRepository.getCanonicalPath());
+        Set<Path> paths = new HashSet<Path>();
+        for (Rule rule : getRules()) {
+            Path pathInSource = rule.getPathInSource(canonicalPathInRepository, factory);
+            if (pathInSource != null) paths.add(pathInSource);
+        }
+        return paths;
+    }
+
+    /**
+     * Get the paths in the repository that correspond to the supplied path within the source. This method computes the paths
+     * given all of the rules. In general, most sources will probably project a node onto a single repository node. However, some
+     * sources may be configured such that the same node in the source is projected into multiple nodes within the repository.
+     * 
+     * @param canonicalPathInSource the canonical path of the node within the source; may not be null
+     * @param factory the path factory; may not be null
+     * @return the set of unique paths in the repository projected from the source path; never null
+     * @throws IllegalArgumentException if the factory reference is null
+     */
+    public Set<Path> getPathsInRepository( Path canonicalPathInSource,
+                                           PathFactory factory ) {
+        ArgCheck.isNotNull(factory, "factory");
+        assert canonicalPathInSource == null ? true : canonicalPathInSource.equals(canonicalPathInSource.getCanonicalPath());
+        Set<Path> paths = new HashSet<Path>();
+        for (Rule rule : getRules()) {
+            Path pathInRepository = rule.getPathInRepository(canonicalPathInSource, factory);
+            if (pathInRepository != null) paths.add(pathInRepository);
+        }
+        return paths;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return this.sourceName.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        if (obj instanceof Projection) {
+            Projection that = (Projection)obj;
+            if (!this.getSourceName().equals(that.getSourceName())) return false;
+            if (!this.getRules().equals(that.getRules())) return false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo( Projection that ) {
+        if (this == that) return 0;
+        int diff = this.getSourceName().compareTo(that.getSourceName());
+        if (diff != 0) return diff;
+        Iterator<Rule> thisIter = this.getRules().iterator();
+        Iterator<Rule> thatIter = that.getRules().iterator();
+        while (thisIter.hasNext() && thatIter.hasNext()) {
+            diff = thisIter.next().compareTo(thatIter.next());
+            if (diff != 0) return diff;
+        }
+        if (thisIter.hasNext()) return 1;
+        if (thatIter.hasNext()) return -1;
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.sourceName);
+        sb.append(" { ");
+        boolean first = true;
+        for (Rule rule : this.getRules()) {
+            if (!first) sb.append(" ; ");
+            sb.append(rule.toString());
+            first = false;
+        }
+        sb.append(" }");
+        return super.toString();
+    }
+
+    /**
+     * A rule used within a project do define how content within a source is project into the federated repository. This mapping
+     * is bi-directional, meaning it's possible to determine
+     * <ul>
+     * <li>the path in repository given a path in source; and</li>
+     * <li>the path in source given a path in repository.</li>
+     * </ul>
+     * 
+     * @author Randall Hauch
+     */
+    @Immutable
+    public static abstract class Rule implements Comparable<Rule> {
+        /**
+         * Get the path in source that is projected from the supplied repository path, or null if the supplied repository path is
+         * not projected into the source.
+         * 
+         * @param pathInRepository the path in the repository; may not be null
+         * @param factory the path factory; may not be null
+         * @return the path in source if it is projected by this rule, or null otherwise
+         */
+        public abstract Path getPathInSource( Path pathInRepository,
+                                              PathFactory factory );
+
+        /**
+         * Get the path in repository that is projected from the supplied source path, or null if the supplied source path is not
+         * projected into the repository.
+         * 
+         * @param pathInSource the path in the source; may not be null
+         * @param factory the path factory; may not be null
+         * @return the path in repository if it is projected by this rule, or null otherwise
+         */
+        public abstract Path getPathInRepository( Path pathInSource,
+                                                  PathFactory factory );
+
+        public abstract String getString( NamespaceRegistry registry,
+                                          TextEncoder encoder );
+
+        public abstract String getString( TextEncoder encoder );
+
+        public abstract String getString();
+    }
+
+    /**
+     * A rule that is defined with a single {@link #getPathInSource() path in source} and a single {@link #getPathInRepository()
+     * path in repository}, and which has a set of {@link #getExceptionsToRule() path exceptions} (relative paths below the path
+     * in source).
+     * 
+     * @author Randall Hauch
+     */
+    @Immutable
+    public static class PathRule extends Rule {
+        /** The path of the content as known to the source */
+        private final Path sourcePath;
+        /** The path where the content is to be placed ("projected") into the repository */
+        private final Path repositoryPath;
+        /** The paths (relative to the source path) that identify exceptions to this rule */
+        private final List<Path> exceptions;
+        private final int hc;
+
+        public PathRule( Path repositoryPath,
+                         Path sourcePath ) {
+            this(repositoryPath, sourcePath, (Path[])null);
+        }
+
+        public PathRule( Path repositoryPath,
+                         Path sourcePath,
+                         Path... exceptions ) {
+            assert sourcePath != null;
+            assert repositoryPath != null;
+            this.sourcePath = sourcePath;
+            this.repositoryPath = repositoryPath;
+            if (exceptions == null || exceptions.length == 0) {
+                this.exceptions = Collections.emptyList();
+            } else {
+                List<Path> exceptionList = new ArrayList<Path>();
+                for (Path exception : exceptions) {
+                    if (exception != null) exceptionList.add(exception);
+                }
+                this.exceptions = Collections.unmodifiableList(exceptionList);
+            }
+            this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions);
+            assert exceptionPathsAreRelative();
+        }
+
+        public PathRule( Path repositoryPath,
+                         Path sourcePath,
+                         List<Path> exceptions ) {
+            assert sourcePath != null;
+            assert repositoryPath != null;
+            this.sourcePath = sourcePath;
+            this.repositoryPath = repositoryPath;
+            if (exceptions == null || exceptions.isEmpty()) {
+                this.exceptions = Collections.emptyList();
+            } else {
+                this.exceptions = Collections.unmodifiableList(new ArrayList<Path>(exceptions));
+            }
+            this.hc = HashCode.compute(sourcePath, repositoryPath, exceptions);
+            assert exceptionPathsAreRelative();
+        }
+
+        private boolean exceptionPathsAreRelative() {
+            if (this.exceptions != null) {
+                for (Path path : this.exceptions) {
+                    if (path.isAbsolute()) return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * The path where the content is to be placed ("projected") into the repository.
+         * 
+         * @return the projected path of the content in the repository; never null
+         */
+        public Path getPathInRepository() {
+            return repositoryPath;
+        }
+
+        /**
+         * The path of the content as known to the source
+         * 
+         * @return the source-specific path of the content; never null
+         */
+        public Path getPathInSource() {
+            return sourcePath;
+        }
+
+        /**
+         * Get whether this rule has any exceptions.
+         * 
+         * @return true if this rule has exceptions, or false if it has none.
+         */
+        public boolean hasExceptionsToRule() {
+            return exceptions.size() != 0;
+        }
+
+        /**
+         * Get the paths that define the exceptions to this rule. These paths are always relative to the
+         * {@link #getPathInSource() path in source}.
+         * 
+         * @return the unmodifiable exception paths; never null but possibly empty
+         */
+        public List<Path> getExceptionsToRule() {
+            return exceptions;
+        }
+
+        /**
+         * @param pathInSource
+         * @return true if the source path is included by this rule
+         */
+        protected boolean includes( Path pathInSource ) {
+            // Check whether the path is outside the source-specific path ...
+            if (this.sourcePath.isAtOrAbove(pathInSource)) {
+
+                // The path is inside the source-specific region, so check the exceptions ...
+                List<Path> exceptions = getExceptionsToRule();
+                if (exceptions.size() != 0) {
+                    Path subpathInSource = pathInSource.relativeTo(this.sourcePath);
+                    if (subpathInSource.size() != 0) {
+                        for (Path exception : exceptions) {
+                            if (subpathInSource.isAtOrBelow(exception)) return false;
+                        }
+                    }
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * <p>
+         * This method considers a path that is at or below the rule's {@link #getPathInSource() source path} to be included,
+         * except if there are {@link #getExceptionsToRule() exceptions} that explicitly disallow the path.
+         * </p>
+         * 
+         * @see org.jboss.dna.connector.federation.Projection.Rule#getPathInSource(Path, PathFactory)
+         */
+        @Override
+        public Path getPathInSource( Path pathInRepository,
+                                     PathFactory factory ) {
+            assert pathInRepository.equals(pathInRepository.getCanonicalPath());
+            // Project the repository path into the equivalent source path ...
+            Path pathInSource = projectPathInRepositoryToPathInSource(pathInRepository, factory);
+
+            // Check whether the source path is included by this rule ...
+            return includes(pathInSource) ? pathInSource : null;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.connector.federation.Projection.Rule#getPathInRepository(org.jboss.dna.spi.graph.Path,
+         *      org.jboss.dna.spi.graph.PathFactory)
+         */
+        @Override
+        public Path getPathInRepository( Path pathInSource,
+                                         PathFactory factory ) {
+            assert pathInSource.equals(pathInSource.getCanonicalPath());
+            // Check whether the source path is included by this rule ...
+            if (!includes(pathInSource)) return null;
+
+            // Project the repository path into the equivalent source path ...
+            return projectPathInSourceToPathInRepository(pathInSource, factory);
+        }
+
+        /**
+         * Convert a path defined in the source system into an equivalent path in the repository system.
+         * 
+         * @param pathInSource the path in the source system, which may include the {@link #getPathInSource()}
+         * @param factory the path factory; may not be null
+         * @return the path in the repository system, which will be normalized and absolute (including the
+         *         {@link #getPathInRepository()}), or null if the path is not at or under the {@link #getPathInSource()}
+         */
+        protected Path projectPathInSourceToPathInRepository( Path pathInSource,
+                                                              PathFactory factory ) {
+            if (!this.sourcePath.isAtOrAbove(pathInSource)) return null;
+            // Remove the leading source path ...
+            Path relativeSourcePath = pathInSource.relativeTo(this.sourcePath);
+            // Prepend the region's root path ...
+            Path result = factory.create(this.repositoryPath, relativeSourcePath);
+            return result.getNormalizedPath();
+        }
+
+        /**
+         * Convert a path defined in the repository system into an equivalent path in the source system.
+         * 
+         * @param pathInRepository the path in the repository system, which may include the {@link #getPathInRepository()}
+         * @param factory the path factory; may not be null
+         * @return the path in the source system, which will be normalized and absolute (including the {@link #getPathInSource()}
+         *         ), or null if the path is not at or under the {@link #getPathInRepository()}
+         */
+        protected Path projectPathInRepositoryToPathInSource( Path pathInRepository,
+                                                              PathFactory factory ) {
+            if (!this.repositoryPath.isAtOrAbove(pathInRepository)) return null;
+            // Find the relative path from the root of this region ...
+            Path pathInRegion = pathInRepository.relativeTo(this.repositoryPath);
+            // Prepend the path in source ...
+            Path result = factory.create(this.sourcePath, pathInRegion);
+            return result.getNormalizedPath();
+        }
+
+        @Override
+        public String getString( NamespaceRegistry registry,
+                                 TextEncoder encoder ) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(this.getPathInRepository().getString(registry, encoder));
+            sb.append(" => ");
+            sb.append(this.getPathInSource().getString(registry, encoder));
+            if (this.getExceptionsToRule().size() != 0) {
+                for (Path exception : this.getExceptionsToRule()) {
+                    sb.append(" $ ");
+                    sb.append(exception.getString(registry, encoder));
+                }
+            }
+            return sb.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.connector.federation.Projection.Rule#getString(org.jboss.dna.common.text.TextEncoder)
+         */
+        @Override
+        public String getString( TextEncoder encoder ) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(this.getPathInRepository().getString(encoder));
+            sb.append(" => ");
+            sb.append(this.getPathInSource().getString(encoder));
+            if (this.getExceptionsToRule().size() != 0) {
+                for (Path exception : this.getExceptionsToRule()) {
+                    sb.append(" $ ");
+                    sb.append(exception.getString(encoder));
+                }
+            }
+            return sb.toString();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.connector.federation.Projection.Rule#getString()
+         */
+        @Override
+        public String getString() {
+            return getString(Path.JSR283_ENCODER);
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#hashCode()
+         */
+        @Override
+        public int hashCode() {
+            return hc;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals( Object obj ) {
+            if (obj == this) return true;
+            if (obj instanceof PathRule) {
+                PathRule that = (PathRule)obj;
+                if (!this.getPathInRepository().equals(that.getPathInRepository())) return false;
+                if (!this.getPathInSource().equals(that.getPathInSource())) return false;
+                if (!this.getExceptionsToRule().equals(that.getExceptionsToRule())) return false;
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Comparable#compareTo(java.lang.Object)
+         */
+        public int compareTo( Rule other ) {
+            if (other == this) return 0;
+            if (other instanceof PathRule) {
+                PathRule that = (PathRule)other;
+                int diff = this.getPathInRepository().compareTo(that.getPathInRepository());
+                if (diff != 0) return diff;
+                diff = this.getPathInSource().compareTo(that.getPathInSource());
+                if (diff != 0) return diff;
+                Iterator<Path> thisIter = this.getExceptionsToRule().iterator();
+                Iterator<Path> thatIter = that.getExceptionsToRule().iterator();
+                while (thisIter.hasNext() && thatIter.hasNext()) {
+                    diff = thisIter.next().compareTo(thatIter.next());
+                    if (diff != 0) return diff;
+                }
+                if (thisIter.hasNext()) return 1;
+                if (thatIter.hasNext()) return -1;
+                return 0;
+            }
+            return other.getClass().getName().compareTo(this.getClass().getName());
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getString();
+        }
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/ProjectionParser.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/ProjectionParser.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/ProjectionParser.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,243 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.connector.federation.Projection.Rule;
+import org.jboss.dna.spi.graph.NamespaceRegistry;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+
+/**
+ * A parser library for {@link Projection projections} and {@link Projection.Rule projection rules}.
+ * 
+ * @author Randall Hauch
+ */
+public class ProjectionParser {
+    private static final ProjectionParser INSTANCE;
+
+    static {
+        INSTANCE = new ProjectionParser();
+        try {
+            INSTANCE.addRuleParser(Projection.class, "parsePathRule");
+            assert INSTANCE.parserMethods.size() == 1;
+        } catch (Throwable err) {
+            Logger.getLogger(Projection.class).error(err, FederationI18n.errorAddingProjectionRuleParseMethod);
+        }
+    }
+
+    /**
+     * Get the shared projection parser, which is by default populated with the standard parser rules.
+     * 
+     * @return the parser; never null
+     */
+    public static ProjectionParser getInstance() {
+        return INSTANCE;
+    }
+
+    private final List<Method> parserMethods = new CopyOnWriteArrayList<Method>();
+    protected final Logger logger;
+
+    public ProjectionParser() {
+        this.logger = Logger.getLogger(this.getClass());
+    }
+
+    /**
+     * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
+     * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
+     * {@link ExecutionEnvironment} environment reference as the second parameter, and should return the resulting {@link Rule}
+     * (or null if the definition format could not be understood by the method. Any exceptions during
+     * {@link Method#invoke(Object, Object...) invocation} will be logged at the
+     * {@link Logger#trace(Throwable, String, Object...) trace} level.
+     * 
+     * @param method the method to be added
+     * @see #addRuleParser(ClassLoader, String, String)
+     */
+    public void addRuleParser( Method method ) {
+        if (method != null) parserMethods.add(method);
+    }
+
+    /**
+     * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
+     * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
+     * {@link ExecutionEnvironment} environment reference as the second parameter, and should return the resulting {@link Rule}
+     * (or null if the definition format could not be understood by the method. Any exceptions during
+     * {@link Method#invoke(Object, Object...) invocation} will be logged at the
+     * {@link Logger#trace(Throwable, String, Object...) trace} level.
+     * 
+     * @param clazz the class on which the static method is defined; may not be null
+     * @param methodName the name of the method
+     * @throws SecurityException if there is a security exception while loading the class or getting the method
+     * @throws NoSuchMethodException if the method does not exist on the class
+     * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
+     *         empty
+     * @see #addRuleParser(Method)
+     */
+    public void addRuleParser( Class<?> clazz,
+                               String methodName ) throws SecurityException, NoSuchMethodException {
+        ArgCheck.isNotNull(clazz, "clazz");
+        ArgCheck.isNotEmpty(methodName, "methodName");
+        parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionEnvironment.class));
+    }
+
+    /**
+     * Add a static method that can be used to parse {@link Rule#getString(NamespaceRegistry, TextEncoder) rule definition
+     * strings}. These methods must be static, must accept a {@link String} definition as the first parameter and an
+     * {@link ExecutionEnvironment} environment reference as the second parameter, and should return the resulting {@link Rule}
+     * (or null if the definition format could not be understood by the method. Any exceptions during
+     * {@link Method#invoke(Object, Object...) invocation} will be logged at the
+     * {@link Logger#trace(Throwable, String, Object...) trace} level.
+     * 
+     * @param classLoader the class loader that should be used to load the class on which the method is defined; may not be null
+     * @param className the name of the class on which the static method is defined; may not be null
+     * @param methodName the name of the method
+     * @throws SecurityException if there is a security exception while loading the class or getting the method
+     * @throws NoSuchMethodException if the method does not exist on the class
+     * @throws ClassNotFoundException if the class could not be found given the supplied class loader
+     * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
+     *         empty
+     * @see #addRuleParser(Method)
+     */
+    public void addRuleParser( ClassLoader classLoader,
+                               String className,
+                               String methodName ) throws SecurityException, NoSuchMethodException, ClassNotFoundException {
+        ArgCheck.isNotNull(classLoader, "classLoader");
+        ArgCheck.isNotEmpty(className, "className");
+        ArgCheck.isNotEmpty(methodName, "methodName");
+        Class<?> clazz = Class.forName(className, true, classLoader);
+        parserMethods.add(clazz.getMethod(methodName, String.class, ExecutionEnvironment.class));
+    }
+
+    /**
+     * Remove the rule parser method.
+     * 
+     * @param method the method to remove
+     * @return true if the method was removed, or false if the method was not a registered rule parser method
+     */
+    public boolean removeRuleParser( Method method ) {
+        return parserMethods.remove(method);
+    }
+
+    /**
+     * Remove the rule parser method.
+     * 
+     * @param declaringClassName the name of the class on which the static method is defined; may not be null
+     * @param methodName the name of the method
+     * @return true if the method was removed, or false if the method was not a registered rule parser method
+     * @throws IllegalArgumentException if the class loader reference is null, or if the class name or method name are null or
+     *         empty
+     */
+    public boolean removeRuleParser( String declaringClassName,
+                                     String methodName ) {
+        ArgCheck.isNotEmpty(declaringClassName, "declaringClassName");
+        ArgCheck.isNotEmpty(methodName, "methodName");
+        for (Method method : parserMethods) {
+            if (method.getName().equals(methodName) && method.getDeclaringClass().getName().equals(declaringClassName)) {
+                return parserMethods.remove(method);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return parserMethods
+     */
+    /*package*/List<Method> getParserMethods() {
+        return Collections.unmodifiableList(parserMethods);
+    }
+
+    /**
+     * Parse the string form of a rule definition and return the rule
+     * 
+     * @param definition the definition of the rule that is to be parsed
+     * @param env the environment in which this method is being executed; may not be null
+     * @return the rule, or null if the definition could not be parsed
+     */
+    public Rule ruleFromString( String definition,
+                                ExecutionEnvironment env ) {
+        ArgCheck.isNotNull(env, "env");
+        definition = definition != null ? definition.trim() : "";
+        if (definition.length() == 0) return null;
+        for (Method method : parserMethods) {
+            try {
+                Rule rule = (Rule)method.invoke(null, definition, env);
+                if (rule != null) {
+                    if (logger.isTraceEnabled()) {
+                        String msg = "Success parsing project rule definition \"{0}\" using {1}";
+                        logger.trace(msg, definition, method);
+                    }
+                    return rule;
+                } else if (logger.isTraceEnabled()) {
+                    String msg = "Unable to parse project rule definition \"{0}\" using {1}";
+                    logger.trace(msg, definition, method);
+                }
+            } catch (Throwable err) {
+                String msg = "Error while parsing project rule definition \"{0}\" using {1}";
+                Logger.getLogger(Projection.class).trace(err, msg, definition, method);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Parse string forms of an arry of rule definitions and return the rules
+     * 
+     * @param env the environment in which this method is being executed; may not be null
+     * @param definitions the definition of the rules that are to be parsed
+     * @return the rule, or null if the definition could not be parsed
+     */
+    public Rule[] rulesFromStrings( ExecutionEnvironment env,
+                                    String... definitions ) {
+        List<Rule> rules = new LinkedList<Rule>();
+        for (String definition : definitions) {
+            Rule rule = ruleFromString(definition, env);
+            if (rule != null) rules.add(rule);
+        }
+        return rules.toArray(new Rule[rules.size()]);
+    }
+
+    /**
+     * Parse a single string containing one or more string forms of rule definitions, and return the rules. The string contains
+     * each rule on a separate line.
+     * 
+     * @param env the environment in which this method is being executed; may not be null
+     * @param definitions the definitions of the rules that are to be parsed, each definition separated by a newline character.
+     * @return the rule, or null if the definition could not be parsed
+     */
+    public Rule[] rulesFromString( ExecutionEnvironment env,
+                                   String definitions ) {
+        List<String> lines = StringUtil.splitLines(definitions);
+        List<Rule> rules = new LinkedList<Rule>();
+        for (String definition : lines) {
+            Rule rule = ruleFromString(definition, env);
+            if (rule != null) rules.add(rule);
+        }
+        return rules.toArray(new Rule[rules.size()]);
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/AbstractContribution.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/AbstractContribution.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/AbstractContribution.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,132 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.contribution;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.Path.Segment;
+
+/**
+ * @author Randall Hauch
+ */
+public abstract class AbstractContribution implements Contribution {
+
+    /**
+     * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID.
+     */
+    private static final long serialVersionUID = 1L;
+
+    protected static final List<Segment> EMPTY_CHILDREN = Collections.emptyList();
+    protected static final Map<Name, Property> EMPTY_PROPERTIES = Collections.emptyMap();
+
+    private final String sourceName;
+    private DateTime expirationTimeInUtc;
+
+    /**
+     * Create a contribution for the source with the supplied name.
+     * 
+     * @param sourceName the name of the source, which may not be null or blank
+     */
+    protected AbstractContribution( String sourceName ) {
+        assert sourceName != null && sourceName.trim().length() != 0;
+        this.sourceName = sourceName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getSourceName() {
+        return this.sourceName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<Segment> getChildren() {
+        return EMPTY_CHILDREN;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<Name, Property> getProperties() {
+        return EMPTY_PROPERTIES;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.contribution.Contribution#isExpired(org.jboss.dna.spi.graph.DateTime)
+     */
+    public boolean isExpired( DateTime utcTime ) {
+        assert utcTime != null;
+        assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time
+        return !expirationTimeInUtc.isAfter(utcTime);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public DateTime getExpirationTimeInUtc() {
+        return this.expirationTimeInUtc;
+    }
+
+    /**
+     * @param expirationTimeInUtc Sets expirationTimeInUtc to the specified value.
+     */
+    public void setExpirationTimeInUtc( DateTime expirationTimeInUtc ) {
+        this.expirationTimeInUtc = expirationTimeInUtc;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation returns the hash code of the {@link #getSourceName() source name}, and is compatible with the
+     * implementation of {@link #equals(Object)}.
+     * </p>
+     */
+    @Override
+    public int hashCode() {
+        return this.sourceName.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This implementation only compares the {@link #getSourceName() source name}.
+     * </p>
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        if (obj instanceof AbstractContribution) {
+            AbstractContribution that = (AbstractContribution)obj;
+            return this.getSourceName().equals(that.getSourceName());
+        }
+        return false;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/Contribution.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/Contribution.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/Contribution.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,87 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.contribution;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.Path.Segment;
+
+/**
+ * The contribution of a source to the information for a single federated node. Users of this interface should treat contributions
+ * as generally being immutable, since some implementation will be immutable and will return immutable {@link #getProperties()
+ * properties} and {@link #getChildren() children} containers. Thus, rather than make changes to an existing contribution, a new
+ * contribution is created to replace the previous contribution.
+ * 
+ * @author Randall Hauch
+ */
+ at NotThreadSafe
+public interface Contribution extends Serializable {
+
+    /**
+     * Get the name of the source that made this contribution.
+     * 
+     * @return the name of the contributing source
+     */
+    public String getSourceName();
+
+    /**
+     * Get the source-specific path of this information.
+     * 
+     * @return the path as known to the source, or null for {@link EmptyContribution}
+     */
+    public Path getPathInSource();
+
+    /**
+     * Determine whether this contribution has expired given the supplied current time.
+     * 
+     * @param utcTime the current time expressed in UTC; may not be null
+     * @return true if at least one contribution has expired, or false otherwise
+     */
+    public boolean isExpired( DateTime utcTime );
+
+    /**
+     * Get the expiration time, already in UTC.
+     * 
+     * @return the expiration time in UTC
+     */
+    public DateTime getExpirationTimeInUtc();
+
+    /**
+     * Get the properties that make up this contribution. This map is immutable.
+     * 
+     * @return the map of properties; never null
+     */
+    public Map<Name, Property> getProperties();
+
+    /**
+     * Get the children that make up this contribution. This list is immutable.
+     * 
+     * @return the list of children; never null
+     */
+    public List<Segment> getChildren();
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/EmptyContribution.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/EmptyContribution.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/EmptyContribution.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.contribution;
+
+import org.jboss.dna.spi.graph.Path;
+
+/**
+ * A source contribution that is empty. In other words, the source has no contribution to make.
+ * <p>
+ * Note that this is different than an unknown contribution, which may occur when a source is added to a federated repository
+ * after the contributions have already been determined for nodes. In this case, the new source's contribution for a node is not
+ * known and must be determined.
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+public class EmptyContribution extends AbstractContribution {
+
+    /**
+     * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID.
+     */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Create a contribution for the source with the supplied name.
+     * 
+     * @param sourceName the name of the source, which may not be null or blank
+     */
+    public EmptyContribution( String sourceName ) {
+        super(sourceName);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.contribution.Contribution#getPathInSource()
+     */
+    public Path getPathInSource() {
+        return null;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/NodeContribution.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/NodeContribution.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/NodeContribution.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,78 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.contribution;
+
+import java.util.ArrayList;
+import java.util.List;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.Path.Segment;
+
+/**
+ * The contribution of a source to the information for a single federated node.
+ * <p>
+ * This class does return a mutable list of {@link #getChildren() children} and mutable map of {@link #getProperties() properties}
+ * .
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+ at NotThreadSafe
+public class NodeContribution extends PropertyContribution {
+
+    /**
+     * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID.
+     */
+    private static final long serialVersionUID = 1L;
+
+    private List<Segment> children;
+
+    /**
+     * Create a contribution of node properties and children from the source with the supplied name.
+     * 
+     * @param sourceName the name of the source, which may not be null or blank
+     * @param pathInSource the path in the source for this contributed information; may not be null
+     * @param properties the properties from the source; may not be null
+     * @param children the children from the source; may not be null or empty
+     */
+    public NodeContribution( String sourceName,
+                             Path pathInSource,
+                             Iterable<Property> properties,
+                             Iterable<Segment> children ) {
+        super(sourceName, pathInSource, properties);
+        this.children = new ArrayList<Segment>(1);
+        for (Segment child : children) {
+            this.children.add(child);
+        }
+    }
+
+    /**
+     * Get the children that make up this contribution. This list is immutable.
+     * 
+     * @return the list of children; never null
+     */
+    @Override
+    public List<Segment> getChildren() {
+        return this.children;
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/PropertyContribution.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/PropertyContribution.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/contribution/PropertyContribution.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,84 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.contribution;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+
+/**
+ * The record of a source contributing only properties to a node.
+ * <p>
+ * This class does return a mutable mutable map of {@link #getProperties() properties}.
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+public class PropertyContribution extends AbstractContribution {
+
+    /**
+     * This is the first version of this class. See the documentation of BasicMergePlan.serialVersionUID.
+     */
+    private static final long serialVersionUID = 1L;
+
+    private final Map<Name, Property> properties;
+    private final Path pathInSource;
+
+    /**
+     * Create a contribution of node properties from the source with the supplied name.
+     * 
+     * @param sourceName the name of the source, which may not be null or blank
+     * @param pathInSource the path in the source for this contributed information; may not be null
+     * @param properties the properties from the source; may not be null
+     */
+    public PropertyContribution( String sourceName,
+                                 Path pathInSource,
+                                 Iterable<Property> properties ) {
+        super(sourceName);
+        assert pathInSource != null;
+        this.properties = new HashMap<Name, Property>();
+        this.pathInSource = pathInSource;
+        for (Property property : properties) {
+            this.properties.put(property.getName(), property);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Map<Name, Property> getProperties() {
+        return this.properties;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.contribution.Contribution#getPathInSource()
+     */
+    public Path getPathInSource() {
+        return pathInSource;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ActsOnProjectedPathCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ActsOnProjectedPathCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ActsOnProjectedPathCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.commands.ActsOnPath;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+
+/**
+ * @author Randall Hauch
+ * @param <T> the command type
+ */
+public class ActsOnProjectedPathCommand<T extends GraphCommand> implements ActsOnPath, GraphCommand {
+
+    private final T delegate;
+    private final Path projectedPath;
+
+    protected ActsOnProjectedPathCommand( T delegate,
+                                          Path projectedPath ) {
+        assert delegate != null;
+        assert projectedPath != null;
+        this.delegate = delegate;
+        this.projectedPath = projectedPath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.ActsOnPath#getPath()
+     */
+    public Path getPath() {
+        return projectedPath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GraphCommand#getError()
+     */
+    public Throwable getError() {
+        return delegate.getError();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GraphCommand#hasError()
+     */
+    public boolean hasError() {
+        return delegate.hasError();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GraphCommand#hasNoError()
+     */
+    public boolean hasNoError() {
+        return delegate.hasNoError();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GraphCommand#isCancelled()
+     */
+    public boolean isCancelled() {
+        return delegate.isCancelled();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GraphCommand#setError(java.lang.Throwable)
+     */
+    public void setError( Throwable error ) {
+        delegate.setError(error);
+    }
+
+    /**
+     * @return delegate
+     */
+    protected T getOriginalCommand() {
+        return delegate;
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/FederatingCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,331 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.connector.federation.FederationI18n;
+import org.jboss.dna.connector.federation.Projection;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+import org.jboss.dna.connector.federation.merge.BasicMergePlan;
+import org.jboss.dna.spi.graph.Binary;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.ValueFactory;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetNodeCommand;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetPropertiesCommand;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+ at NotThreadSafe
+public class FederatingCommandExecutor extends AbstractCommandExecutor {
+
+    private final Name mergePlanPropertyName;
+    private final Projection cacheProjection;
+    private final List<Projection> sourceProjections;
+    private final Set<String> sourceNames;
+    private final RepositoryConnectionFactories connectionFactories;
+    /** The set of all connections, including the cache connection */
+    private final Map<String, RepositoryConnection> connectionsBySourceName;
+    /** A direct reference to the cache connection */
+    private RepositoryConnection cacheConnection;
+
+    /**
+     * Create a command executor that federates (merges) the information from multiple sources described by the source
+     * projections. The resulting command executor does not first consult a cache for the merged information; if a cache is
+     * desired, see
+     * {@link #FederatingCommandExecutor(ExecutionEnvironment, String, Projection, List, RepositoryConnectionFactories)
+     * constructor} that takes a {@link Projection cache projection}.
+     * 
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @param sourceProjections the source projections; may not be null
+     * @param connectionFactories the factory for connection factory instances
+     */
+    public FederatingCommandExecutor( ExecutionEnvironment env,
+                                      String sourceName,
+                                      List<Projection> sourceProjections,
+                                      RepositoryConnectionFactories connectionFactories ) {
+        this(env, sourceName, null, sourceProjections, connectionFactories);
+    }
+
+    /**
+     * Create a command executor that federates (merges) the information from multiple sources described by the source
+     * projections. The resulting command executor will use the supplied {@link Projection cache projection} to identify the
+     * {@link Projection#getSourceName() repository source} for the cache as well as the {@link Projection#getRules() rules} for
+     * how the paths are mapped in the cache. This cache will be consulted first for the requested information, and will be kept
+     * up to date as changes are made to the federated information.
+     * 
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @param cacheProjection the projection used for the cached information; may be null if there is no cache
+     * @param sourceProjections the source projections; may not be null
+     * @param connectionFactories the factory for connection factory instances
+     */
+    public FederatingCommandExecutor( ExecutionEnvironment env,
+                                      String sourceName,
+                                      Projection cacheProjection,
+                                      List<Projection> sourceProjections,
+                                      RepositoryConnectionFactories connectionFactories ) {
+        super(env, sourceName);
+        assert sourceProjections != null;
+        assert connectionFactories != null;
+        this.cacheProjection = cacheProjection;
+        this.sourceProjections = sourceProjections;
+        this.connectionFactories = connectionFactories;
+        this.connectionsBySourceName = new HashMap<String, RepositoryConnection>();
+        this.mergePlanPropertyName = env.getValueFactories().getNameFactory().create("dna:mergePlan");
+        this.sourceNames = new HashSet<String>();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#close()
+     */
+    @Override
+    public void close() throws InterruptedException {
+        try {
+            super.close();
+        } finally {
+            // Make sure to close ALL open connections ...
+            for (RepositoryConnection connection : connectionsBySourceName.values()) {
+                if (connection == null) continue;
+                try {
+                    connection.close();
+                } catch (Throwable t) {
+                    Logger.getLogger(getClass()).debug("Error while closing connection to {0}", connection.getSourceName());
+                }
+            }
+            connectionsBySourceName.clear();
+        }
+    }
+
+    protected RepositoryConnection getConnectionToCache() throws RepositorySourceException, InterruptedException {
+        if (this.cacheConnection == null) {
+            this.cacheConnection = getConnection(this.cacheProjection);
+        }
+        assert this.cacheConnection != null;
+        return this.cacheConnection;
+    }
+
+    protected RepositoryConnection getConnection( Projection projection ) throws RepositorySourceException, InterruptedException {
+        String sourceName = projection.getSourceName();
+        RepositoryConnection connection = connectionsBySourceName.get(sourceName);
+        if (connection == null) {
+            RepositoryConnectionFactory connectionFactory = connectionFactories.getConnectionFactory(sourceName);
+            if (connectionFactory != null) {
+                connection = connectionFactory.getConnection();
+            }
+            connectionsBySourceName.put(sourceName, connection);
+        }
+        return connection;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This class overrides the {@link AbstractCommandExecutor#execute(GetNodeCommand) default behavior} and instead processes the
+     * command in a more efficient manner.
+     * </p>
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    @Override
+    public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        BasicGetNodeCommand nodeInfo = getNode(command.getPath());
+        for (Property property : nodeInfo.getProperties().values()) {
+            command.setProperty(property);
+        }
+        command.setChildren(nodeInfo.getChildren());
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
+     */
+    @Override
+    public void execute( GetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        BasicGetNodeCommand nodeInfo = getNode(command.getPath());
+        for (Property property : nodeInfo.getProperties().values()) {
+            command.setProperty(property);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetChildrenCommand)
+     */
+    @Override
+    public void execute( GetChildrenCommand command ) throws RepositorySourceException, InterruptedException {
+        BasicGetNodeCommand nodeInfo = getNode(command.getPath());
+        command.setChildren(nodeInfo.getChildren());
+    }
+
+    /**
+     * Get the node information from the underlying sources or, if possible, from the cache.
+     * 
+     * @param path the path of the node to be returned
+     * @return the node information
+     * @throws RepositorySourceException
+     * @throws InterruptedException
+     */
+    protected BasicGetNodeCommand getNode( Path path ) throws RepositorySourceException, InterruptedException {
+        // Check the cache first ...
+        RepositoryConnection cacheConnection = getConnectionToCache();
+        BasicGetNodeCommand fromCache = new BasicGetNodeCommand(path);
+        cacheConnection.execute(getEnvironment(), fromCache);
+        if (fromCache.hasError()) return fromCache;
+
+        // Look up the merge plan ...
+        BasicMergePlan basicMergePlan = getMergePlan(fromCache);
+        if (basicMergePlan != null) {
+            if (isCurrent(path, basicMergePlan)) return fromCache;
+            // Some of the merge plan is out of date, so we need to read the information from those regions that are expired ...
+            BasicMergePlan newMergePlan = new BasicMergePlan();
+            // for (Projection projection : sourceProjections) {
+            // // Does this region apply to the path ...
+            // if (projection.appliesTo(path)) {
+            // // Get any existing contribution ...
+            // Contribution contribution = mergePlan.getContributionFrom(region.getSourceName());
+            // if (contribution == null || contribution.isExpired(getCurrentTimeInUtc())) {
+            // contribution = getContribution(contribution.getSourceName(), path);
+            // }
+            // newMergePlan.addContribution(contribution);
+            // }
+            // }
+            // for ( Contribution contribution : mergePlan.getContributions() ) {
+            // // Is the contribution still represented by a region?
+            //                
+            // if ( contribution.isExpired(getCurrentTimeInUtc())) {
+            // contribution = getContribution(contribution.getSourceName(),path);
+            // }
+            // newMergePlan.addContribution(contribution);
+            // }
+
+            // What about other (new) regions?
+        } else {
+            // At this point, there is no merge plan, so read information from the sources ...
+            // for (FederatedRegion region : regions) {
+            // if (!region.appliesTo(path)) continue;
+            // // Issue a command to get the node from the
+            // // BasicGetNode
+            // }
+        }
+        // And read the information from any new region ...
+        return null;
+    }
+
+    protected Contribution getContribution( String sourceName,
+                                            Path path ) {
+        return null;
+    }
+
+    protected BasicMergePlan getMergePlan( BasicGetPropertiesCommand command ) {
+        Property mergePlanProperty = command.getProperties().get(mergePlanPropertyName);
+        if (mergePlanProperty == null || mergePlanProperty.isEmpty()) {
+            return null;
+        }
+        ValueFactory<Binary> binaryFactory = getEnvironment().getValueFactories().getBinaryFactory();
+        Binary binaryValue = binaryFactory.create(mergePlanProperty.getValues().next());
+        binaryValue.acquire();
+        ObjectInputStream stream = null;
+        BasicMergePlan basicMergePlan = null;
+        RepositorySourceException error = null;
+        try {
+            stream = new ObjectInputStream(binaryValue.getStream());
+            basicMergePlan = (BasicMergePlan)stream.readObject();
+        } catch (Throwable err) {
+            I18n msg = FederationI18n.errorReadingMergePlan;
+            error = new RepositorySourceException(getSourceName(), msg.text(command.getPath()), err);
+            throw error;
+        } finally {
+            try {
+                if (stream != null) stream.close();
+            } catch (Throwable err) {
+                if (error == null) {
+                    I18n msg = FederationI18n.errorReadingMergePlan;
+                    error = new RepositorySourceException(getSourceName(), msg.text(command.getPath()), err);
+                    throw error;
+                }
+            } finally {
+                binaryValue.release();
+            }
+        }
+        return basicMergePlan;
+    }
+
+    /**
+     * Determine if the supplied plan is considered current
+     * 
+     * @param path the path of the node at which (or below which) the merge plan applies
+     * @param plan the merge plan
+     * @return true if the merge plan is current, or false if it needs to be (at least partially) rebuilt
+     */
+    protected boolean isCurrent( Path path,
+                                 BasicMergePlan plan ) {
+        // First check the time ...
+        DateTime now = getCurrentTimeInUtc();
+        if (plan.isExpired(now)) return false;
+
+        // Does the plan have any contributions from sources that don't exist ?
+        for (String contributingSource : plan.getNamesOfContributingSources()) {
+            if (!sourceNames.contains(contributingSource)) return false;
+        }
+
+        // Determine if any new source projections exists that aren't part of the plan ...
+        for (String sourceName : sourceNames) {
+            if (plan.isSource(sourceName)) continue;
+            // The source is new ... see whether there are any regions that apply ...
+            // for (FederatedRegion region : this.regionsBySourceName.get(sourceName)) {
+            // // If the region's path is not at/above the path, the region doesn't matter
+            // if (!region.appliesTo(path)) continue;
+            // // The region applies to the path ...
+            // return false;
+            // }
+        }
+        return true;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyBranchCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyBranchCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,37 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedCopyBranchCommand extends ProjectedCopyNodeCommand implements CopyBranchCommand {
+
+    public ProjectedCopyBranchCommand( CopyBranchCommand delegate,
+                                       Path projectedPath,
+                                       Path newProjectedPath ) {
+        super(delegate, projectedPath, newProjectedPath);
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyNodeCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyNodeCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCopyNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedCopyNodeCommand extends ActsOnProjectedPathCommand<CopyNodeCommand> implements CopyNodeCommand {
+
+    private final Path newProjectedPath;
+
+    public ProjectedCopyNodeCommand( CopyNodeCommand delegate,
+                                     Path projectedPath,
+                                     Path newProjectedPath ) {
+        super(delegate, projectedPath);
+        assert newProjectedPath != null;
+        this.newProjectedPath = newProjectedPath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.CopyNodeCommand#getNewPath()
+     */
+    public Path getNewPath() {
+        return newProjectedPath;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCreateNodeCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCreateNodeCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedCreateNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
+import org.jboss.dna.spi.graph.commands.NodeConflictBehavior;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedCreateNodeCommand extends ActsOnProjectedPathCommand<CreateNodeCommand> implements CreateNodeCommand {
+
+    public ProjectedCreateNodeCommand( CreateNodeCommand delegate,
+                                       Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.CreateNodeCommand#getConflictBehavior()
+     */
+    public NodeConflictBehavior getConflictBehavior() {
+        return getOriginalCommand().getConflictBehavior();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.CreateNodeCommand#getProperties()
+     */
+    public Iterable<Property> getProperties() {
+        return getOriginalCommand().getProperties();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo( CreateNodeCommand that ) {
+        if (this == that) return 0;
+        return this.getPath().compareTo(that.getPath());
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedDeleteBranchCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedDeleteBranchCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedDeleteBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,37 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedDeleteBranchCommand extends ActsOnProjectedPathCommand<DeleteBranchCommand> implements DeleteBranchCommand {
+
+    public ProjectedDeleteBranchCommand( DeleteBranchCommand delegate,
+                                         Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetChildrenCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetChildrenCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetChildrenCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,117 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import java.util.Iterator;
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Path.Segment;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedGetChildrenCommand extends ActsOnProjectedPathCommand<GetChildrenCommand> implements GetChildrenCommand {
+
+    /**
+     */
+    private static final long serialVersionUID = 1L;
+
+    public ProjectedGetChildrenCommand( GetChildrenCommand delegate,
+                                        Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetChildrenCommand#setChild(org.jboss.dna.spi.graph.Name)
+     */
+    public void setChild( Name nameOfChild ) {
+        getOriginalCommand().setChild(nameOfChild);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetChildrenCommand#setChildren(java.util.Iterator)
+     */
+    public void setChildren( Iterator<Segment> namesOfChildren ) {
+        getOriginalCommand().setChildren(namesOfChildren);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetChildrenCommand#setChildren(java.lang.Iterable)
+     */
+    public void setChildren( Iterable<Segment> namesOfChildren ) {
+        getOriginalCommand().setChildren(namesOfChildren);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetChildrenCommand#setChildren(org.jboss.dna.spi.graph.Path.Segment[])
+     */
+    public void setChildren( Segment... namesOfChildren ) {
+        getOriginalCommand().setChildren(namesOfChildren);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetChildrenCommand#setNoChildren()
+     */
+    public void setNoChildren() {
+        getOriginalCommand().setNoChildren();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#getCachePolicy()
+     */
+    public CachePolicy getCachePolicy() {
+        return getOriginalCommand().getCachePolicy();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#getTimeLoaded()
+     */
+    public long getTimeLoaded() {
+        return getOriginalCommand().getTimeLoaded();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#setCachePolicy(org.jboss.dna.spi.cache.CachePolicy)
+     */
+    public void setCachePolicy( CachePolicy cachePolicy ) {
+        getOriginalCommand().setCachePolicy(cachePolicy);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetNodeCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetNodeCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedGetNodeCommand extends ProjectedGetChildrenCommand implements GetNodeCommand {
+
+    /**
+     */
+    private static final long serialVersionUID = 1L;
+
+    public ProjectedGetNodeCommand( GetChildrenCommand delegate,
+                                    Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetPropertiesCommand#setProperty(org.jboss.dna.spi.graph.Property)
+     */
+    public void setProperty( Property property ) {
+        ((GetNodeCommand)getOriginalCommand()).setProperty(property);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetPropertiesCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetPropertiesCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedGetPropertiesCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,80 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedGetPropertiesCommand extends ActsOnProjectedPathCommand<GetPropertiesCommand>
+    implements GetPropertiesCommand {
+
+    /**
+     */
+    private static final long serialVersionUID = 1L;
+
+    public ProjectedGetPropertiesCommand( GetPropertiesCommand delegate,
+                                          Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#getCachePolicy()
+     */
+    public CachePolicy getCachePolicy() {
+        return getOriginalCommand().getCachePolicy();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#getTimeLoaded()
+     */
+    public long getTimeLoaded() {
+        return getOriginalCommand().getTimeLoaded();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.cache.Cacheable#setCachePolicy(org.jboss.dna.spi.cache.CachePolicy)
+     */
+    public void setCachePolicy( CachePolicy cachePolicy ) {
+        getOriginalCommand().setCachePolicy(cachePolicy);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.GetPropertiesCommand#setProperty(org.jboss.dna.spi.graph.Property)
+     */
+    public void setProperty( Property property ) {
+        getOriginalCommand().setProperty(property);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedMoveBranchCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedMoveBranchCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedMoveBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
+import org.jboss.dna.spi.graph.commands.NodeConflictBehavior;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedMoveBranchCommand extends ActsOnProjectedPathCommand<MoveBranchCommand> implements MoveBranchCommand {
+
+    private final Path newProjectedPath;
+
+    public ProjectedMoveBranchCommand( MoveBranchCommand delegate,
+                                       Path projectedPath,
+                                       Path newProjectedPath ) {
+        super(delegate, projectedPath);
+        assert newProjectedPath != null;
+        this.newProjectedPath = newProjectedPath;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.MoveBranchCommand#getConflictBehavior()
+     */
+    public NodeConflictBehavior getConflictBehavior() {
+        return getOriginalCommand().getConflictBehavior();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.MoveBranchCommand#getNewPath()
+     */
+    public Path getNewPath() {
+        return this.newProjectedPath;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedRecordBranchCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedRecordBranchCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedRecordBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,82 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import java.util.Iterator;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedRecordBranchCommand extends ActsOnProjectedPathCommand<RecordBranchCommand> implements RecordBranchCommand {
+
+    public ProjectedRecordBranchCommand( RecordBranchCommand delegate,
+                                         Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.RecordBranchCommand#record(org.jboss.dna.spi.graph.Path, java.lang.Iterable)
+     */
+    public boolean record( Path path,
+                           Iterable<Property> properties ) {
+        path = relativePathFrom(path);
+        return getOriginalCommand().record(path, properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.RecordBranchCommand#record(org.jboss.dna.spi.graph.Path, java.util.Iterator)
+     */
+    public boolean record( Path path,
+                           Iterator<Property> properties ) {
+        path = relativePathFrom(path);
+        return getOriginalCommand().record(path, properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.RecordBranchCommand#record(org.jboss.dna.spi.graph.Path,
+     *      org.jboss.dna.spi.graph.Property[])
+     */
+    public boolean record( Path path,
+                           Property... properties ) {
+        path = relativePathFrom(path);
+        return getOriginalCommand().record(path, properties);
+    }
+
+    protected Path relativePathFrom( Path path ) {
+        if (path == null) return null;
+        if (path.isAbsolute()) {
+            // The path is absolute, so compute the relative path w/r/t the new path ...
+            path = path.relativeTo(this.getPath());
+        }
+        return path;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedSetPropertiesCommand.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedSetPropertiesCommand.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/ProjectedSetPropertiesCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectedSetPropertiesCommand extends ActsOnProjectedPathCommand<SetPropertiesCommand>
+    implements SetPropertiesCommand {
+
+    public ProjectedSetPropertiesCommand( SetPropertiesCommand delegate,
+                                          Path projectedPath ) {
+        super(delegate, projectedPath);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.ActsOnProperties#getProperties()
+     */
+    public Iterable<Property> getProperties() {
+        return getOriginalCommand().getProperties();
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/SingleProjectionCommandExecutor.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/SingleProjectionCommandExecutor.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/executor/SingleProjectionCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,247 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.executor;
+
+import java.util.Set;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.connector.federation.Projection;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
+import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
+import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
+import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
+import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
+import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+ at NotThreadSafe
+public class SingleProjectionCommandExecutor extends AbstractCommandExecutor {
+
+    private final Projection projection;
+    private final PathFactory pathFactory;
+    private final RepositoryConnectionFactories factories;
+    private RepositoryConnection connection;
+
+    /**
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @param projection the projection used for the cached information; may not be null and must have exactly one
+     *        {@link Projection#getRules() rule}
+     * @param connectionFactories the factory for connection factory instances
+     */
+    public SingleProjectionCommandExecutor( ExecutionEnvironment env,
+                                            String sourceName,
+                                            Projection projection,
+                                            RepositoryConnectionFactories connectionFactories ) {
+        this(env, sourceName, null, projection, connectionFactories);
+    }
+
+    /**
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @param now the current time; may be null if the system time is to be used
+     * @param projection the projection used for the cached information; may not be null and must have exactly one
+     *        {@link Projection#getRules() rule}
+     * @param connectionFactories the factory for connection factory instances
+     */
+    public SingleProjectionCommandExecutor( ExecutionEnvironment env,
+                                            String sourceName,
+                                            DateTime now,
+                                            Projection projection,
+                                            RepositoryConnectionFactories connectionFactories ) {
+        super(env, sourceName, now);
+        assert connectionFactories != null;
+        assert projection != null;
+        assert projection.getRules().size() == 1;
+        this.projection = projection;
+        this.factories = connectionFactories;
+        this.pathFactory = env.getValueFactories().getPathFactory();
+        assert this.pathFactory != null;
+    }
+
+    protected RepositoryConnection getConnection() throws RepositorySourceException, InterruptedException {
+        if (connection == null) {
+            // Create a connection ...
+            RepositoryConnectionFactory connectionFactory = this.factories.getConnectionFactory(this.projection.getSourceName());
+            connection = connectionFactory.getConnection();
+        }
+        return connection;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#close()
+     */
+    @Override
+    public void close() throws InterruptedException {
+        if (this.connection != null) {
+            try {
+                this.connection.close();
+            } finally {
+                this.connection = null;
+            }
+        }
+        super.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetChildrenCommand)
+     */
+    @Override
+    public void execute( GetChildrenCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedGetChildrenCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
+     */
+    @Override
+    public void execute( GetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedGetPropertiesCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    @Override
+    public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedGetNodeCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CreateNodeCommand)
+     */
+    @Override
+    public void execute( CreateNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedCreateNodeCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.SetPropertiesCommand)
+     */
+    @Override
+    public void execute( SetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedSetPropertiesCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.DeleteBranchCommand)
+     */
+    @Override
+    public void execute( DeleteBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedDeleteBranchCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.MoveBranchCommand)
+     */
+    @Override
+    public void execute( MoveBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        Path newPathInSource = getPathInSource(command.getNewPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedMoveBranchCommand(command, pathInSource, newPathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.RecordBranchCommand)
+     */
+    @Override
+    public void execute( RecordBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedRecordBranchCommand(command, pathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyBranchCommand)
+     */
+    @Override
+    public void execute( CopyBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        Path newPathInSource = getPathInSource(command.getNewPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedCopyBranchCommand(command, pathInSource, newPathInSource));
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyNodeCommand)
+     */
+    @Override
+    public void execute( CopyNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        Path pathInSource = getPathInSource(command.getPath());
+        Path newPathInSource = getPathInSource(command.getNewPath());
+        getConnection().execute(this.getEnvironment(), new ProjectedCopyNodeCommand(command, pathInSource, newPathInSource));
+    }
+
+    protected Path getPathInSource( Path pathInRepository ) {
+        Set<Path> paths = this.projection.getPathsInSource(pathInRepository, pathFactory);
+        if (!paths.isEmpty()) {
+            return paths.iterator().next();
+        }
+        return this.projection.getPathsInSource(pathInRepository, pathFactory).iterator().next();
+    }
+
+    protected Path getPathInRepository( Path pathInSource ) {
+        return this.projection.getPathsInRepository(pathInSource, pathFactory).iterator().next();
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicFederatedNode.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicFederatedNode.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicFederatedNode.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,148 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Property;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class BasicFederatedNode implements FederatedNode {
+
+    private final UUID uuid;
+
+    // private Name name;
+
+    /**
+     * Create a new federated node instance.
+     * 
+     * @param uuid the UUID; may not be null
+     */
+    public BasicFederatedNode( UUID uuid ) {
+        assert uuid != null;
+        this.uuid = uuid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public UUID getUuid() {
+        return uuid;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Name getName() {
+        return null;
+    }
+
+    /**
+     * @param name Sets name to the specified value; may not be null
+     */
+    public void setName( Name name ) {
+        assert name != null;
+        // this.name = name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> getContributingSources() {
+        Set<String> names = new HashSet<String>();
+        return names;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<Property> getProperties() {
+        // Compute merged properties ...
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Property getProperty( Name propertyName ) {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getPropertyCount() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<Name> getPropertyNames() {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void removeAllProperties() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setAllProperties( Property... properties ) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setAllProperties( Iterable<Property> properties ) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setProperties( Property... properties ) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setProperties( Iterable<Property> properties ) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Property setPropertyIfAbsent( Property property ) {
+        return null;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicMergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicMergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/BasicMergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,205 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.io.InvalidClassException;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+import org.jboss.dna.connector.federation.contribution.EmptyContribution;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Property;
+
+/**
+ * This class represents the details about how information from different sources are merged into a single federated node.
+ * <p>
+ * A merge plan basically consists of the individual contribution from each source and the information about how these
+ * contributions were merged into the single federated node.
+ * </p>
+ * <p>
+ * Merge plans are designed to be {@link Serializable serializable}, as they are persisted on the federated node and deserialized
+ * to assist in the management of the federated node.
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+ at NotThreadSafe
+public class BasicMergePlan implements Serializable {
+
+    /**
+     * Define the earliest version of this class that is supported. The Java runtime, upon deserialization, compares the
+     * serialized object's version to this, and if less than this version will throw a {@link InvalidClassException}. If, however,
+     * the serialized object's version is compatible with this class, it will be deserialized successfully.
+     * <p>
+     * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678">Sun's documentation</a> describes
+     * the following changes can be made without negatively affecting the deserialization of older versions:
+     * <ul>
+     * <li>Adding fields - When the class being reconstituted has a field that does not occur in the stream, that field in the
+     * object will be initialized to the default value for its type. If class-specific initialization is needed, the class may
+     * provide a readObject method that can initialize the field to nondefault values.</i>
+     * <li>Adding classes - The stream will contain the type hierarchy of each object in the stream. Comparing this hierarchy in
+     * the stream with the current class can detect additional classes. Since there is no information in the stream from which to
+     * initialize the object, the class's fields will be initialized to the default values.</i>
+     * <li>Removing classes - Comparing the class hierarchy in the stream with that of the current class can detect that a class
+     * has been deleted. In this case, the fields and objects corresponding to that class are read from the stream. Primitive
+     * fields are discarded, but the objects referenced by the deleted class are created, since they may be referred to later in
+     * the stream. They will be garbage-collected when the stream is garbage-collected or reset.</i>
+     * <li>Adding writeObject/readObject methods - If the version reading the stream has these methods then readObject is
+     * expected, as usual, to read the required data written to the stream by the default serialization. It should call
+     * defaultReadObject first before reading any optional data. The writeObject method is expected as usual to call
+     * defaultWriteObject to write the required data and then may write optional data.</i>
+     * <li>Removing writeObject/readObject methods - If the class reading the stream does not have these methods, the required
+     * data will be read by default serialization, and the optional data will be discarded.</i>
+     * <li>Adding java.io.Serializable - This is equivalent to adding types. There will be no values in the stream for this class
+     * so its fields will be initialized to default values. The support for subclassing nonserializable classes requires that the
+     * class's supertype have a no-arg constructor and the class itself will be initialized to default values. If the no-arg
+     * constructor is not available, the InvalidClassException is thrown.</i>
+     * <li>Changing the access to a field - The access modifiers public, package, protected, and private have no effect on the
+     * ability of serialization to assign values to the fields.</i>
+     * <li>Changing a field from static to nonstatic or transient to nontransient - When relying on default serialization to
+     * compute the serializable fields, this change is equivalent to adding a field to the class. The new field will be written to
+     * the stream but earlier classes will ignore the value since serialization will not assign values to static or transient
+     * fields.</i>
+     * </ul>
+     * All other kinds of modifications should be avoided.
+     * </p>
+     */
+    private static final long serialVersionUID = 1L;
+
+    private final Map<String, Contribution> contributions = new HashMap<String, Contribution>();
+    private Map<Name, Property> annotations = null;
+    private DateTime expirationTimeInUtc;
+
+    /**
+     * Create this version
+     */
+    public BasicMergePlan() {
+    }
+
+    /**
+     * Determine whether this merge plan has expired given the supplied current time. The {@link #getExpirationTimeInUtc()
+     * expiration time} is the earliest time that any of the {@link #getContributionFrom(String) contributions}
+     * {@link Contribution#getExpirationTimeInUtc()}.
+     * 
+     * @param utcTime the current time expressed in UTC; may not be null
+     * @return true if at least one contribution has expired, or false otherwise
+     */
+    public boolean isExpired( DateTime utcTime ) {
+        assert utcTime != null;
+        assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time
+        return !expirationTimeInUtc.isAfter(utcTime);
+    }
+
+    /**
+     * Get the expiration time (in UTC) that is the earliest time that any of the {@link #getContributionFrom(String)
+     * contributions} {@link Contribution#getExpirationTimeInUtc()}.
+     * 
+     * @return the expiration time in UTC, or null if there is no known expiration time
+     */
+    public DateTime getExpirationTimeInUtc() {
+        return expirationTimeInUtc;
+    }
+
+    /**
+     * Get the contribution from the source with the supplied name. Note that contributions always include sources that contribute
+     * information and sources that contribute no information. If a source is not included in this list, its contributions are
+     * <i>unknown</i>; that is, it is unknown whether that source does or does not contribute to the node.
+     * 
+     * @param sourceName the name of the source
+     * @return the contribution, or null if the contribution of the source is unknown
+     */
+    public Contribution getContributionFrom( String sourceName ) {
+        return contributions.get(sourceName);
+    }
+
+    /**
+     * Return whether the named source was consulted for a contribution.
+     * 
+     * @param sourceName the name of the source
+     * @return true if the source has some {@link Contribution contribution} (even if it is an {@link EmptyContribution})
+     */
+    public boolean isSource( String sourceName ) {
+        return contributions.containsKey(sourceName);
+    }
+
+    /**
+     * Get the names of the contributing sources.
+     * 
+     * @return the names of the sources that have some contribution
+     */
+    public Set<String> getNamesOfContributingSources() {
+        return contributions.keySet();
+    }
+
+    /**
+     * Add the supplied contribution, replacing and returning any previous contribution for the same source.
+     * 
+     * @param contribution the new contribution
+     * @return the previous contribution for the source, or null if there was no previous contribution.
+     */
+    public Contribution addContribution( Contribution contribution ) {
+        assert contribution != null;
+        Contribution previous = contributions.put(contribution.getSourceName(), contribution);
+        DateTime contributionExpirationInUtc = contribution.getExpirationTimeInUtc();
+        if (expirationTimeInUtc == null || contributionExpirationInUtc.isBefore(expirationTimeInUtc)) {
+            expirationTimeInUtc = contributionExpirationInUtc;
+        }
+        return previous;
+    }
+
+    /**
+     * 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
+     * 
+     * @param name the name of the annotation
+     * @return the existing annotation, or null if there is no annotation with the supplied name
+     * @see #setAnnotation(Property)
+     */
+    public Property getAnnotation( Name name ) {
+        if (name == null) return null;
+        if (this.annotations == null) return null;
+        return this.annotations.get(name);
+    }
+
+    /**
+     * Set the plan annotation property. This method replaces and returns any existing annotation property with the same name.
+     * This method also returns immediately if the supplied annotation is null.
+     * 
+     * @param annotation the new annotation
+     * @return the previous annotation property with the same name, or null if there was no previous annotation property for the
+     *         name
+     * @see #getAnnotation(Name)
+     */
+    public Property setAnnotation( Property annotation ) {
+        if (annotation == null) return null;
+        if (this.annotations == null) {
+            this.annotations = new HashMap<Name, Property>();
+        }
+        return this.annotations.put(annotation.getName(), annotation);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/DoubleContributionMergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/DoubleContributionMergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/DoubleContributionMergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,115 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class DoubleContributionMergePlan extends MergePlan {
+
+    private static final long serialVersionUID = 1L;
+    private final Contribution contribution1;
+    private final Contribution contribution2;
+
+    /**
+     * @param contribution1 the first contribution for this merge plan
+     * @param contribution2 the second contribution for this merge plan
+     */
+    public DoubleContributionMergePlan( Contribution contribution1,
+                                        Contribution contribution2 ) {
+        assert contribution1 != null;
+        assert contribution2 != null;
+        this.contribution1 = contribution1;
+        this.contribution2 = contribution2;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionCount()
+     */
+    @Override
+    public int getContributionCount() {
+        return 2;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionFrom(java.lang.String)
+     */
+    @Override
+    public Contribution getContributionFrom( String sourceName ) {
+        if (contribution1.getSourceName().equals(sourceName)) return contribution1;
+        if (contribution2.getSourceName().equals(sourceName)) return contribution2;
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Iterable#iterator()
+     */
+    public Iterator<Contribution> iterator() {
+        return new Iterator<Contribution>() {
+            private int next = 2;
+
+            public boolean hasNext() {
+                return next > 0;
+            }
+
+            @SuppressWarnings( "synthetic-access" )
+            public Contribution next() {
+                if (next == 2) {
+                    next = 1;
+                    return contribution2;
+                }
+                if (next == 1) {
+                    next = 0;
+                    return contribution1;
+                }
+                throw new NoSuchElementException();
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#isSource(java.lang.String)
+     */
+    @Override
+    public boolean isSource( String sourceName ) {
+        return contribution1.getSourceName().equals(sourceName) || contribution2.getSourceName().equals(sourceName);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/FederatedNode.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/FederatedNode.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/FederatedNode.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,140 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.UUID;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Property;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public interface FederatedNode {
+    /**
+     * Get the unique identifier for this federated node.
+     * 
+     * @return the UUID; never null
+     */
+    UUID getUuid();
+
+    /**
+     * Get the name of this node.
+     * 
+     * @return the node name; never null
+     */
+    Name getName();
+
+    /**
+     * Get the property with the given name.
+     * 
+     * @param propertyName the property name
+     * @return the property, or null if the property does not exist
+     * @throws IllegalArgumentException if the name is null
+     */
+    Property getProperty( Name propertyName );
+
+    /**
+     * Get the number of properties on this node. This value is technically an estimate, as it may not exactly match the number of
+     * properties returned by {@link #getProperties()} or {@link #getPropertyNames()}.
+     * 
+     * @return the approximate number of properties.
+     */
+    int getPropertyCount();
+
+    /**
+     * Get the properties. This method returns a consistent set of properties at the moment this method is called, and is not
+     * affected by additions or change to the properties. In other words, it is safe for concurrent operations and is not a
+     * fail-fast iterator.
+     * 
+     * @return the properties on this node via an immutable iterator
+     */
+    Iterator<Property> getProperties();
+
+    /**
+     * Get the names of the properties for this node. This method returns a consistent set of names at the moment this method is
+     * called, and is not affected by additions or change to the properties. In other words, it is safe for concurrent operations
+     * and is not a fail-fast iterator.
+     * 
+     * @return the property names via an immutable iterator
+     */
+    Iterator<Name> getPropertyNames();
+
+    /**
+     * Set the property if it is not already set.
+     * 
+     * @param property the property
+     * @return the existing property, or null if there was no property and the supplied property was set
+     * @throws IllegalArgumentException if the property is null
+     */
+    Property setPropertyIfAbsent( Property property );
+
+    /**
+     * Set the supplied properties. This method will overwrite any existing properties with the new properties if they have the
+     * same {@link Property#getName() property name}. This method ignores any null property references, and does nothing if there
+     * are no properties supplied.
+     * 
+     * @param properties the properties that should be set
+     */
+    void setProperties( Property... properties );
+
+    /**
+     * Set the supplied properties. This method will overwrite any existing properties with the new properties if they have the
+     * same {@link Property#getName() property name}. This method ignores any null property references, and does nothing if there
+     * are no properties supplied.
+     * 
+     * @param properties the properties that should be set
+     */
+    void setProperties( Iterable<Property> properties );
+
+    /**
+     * Replace all existing properties with the supplied properties. This method ignores any null property references, and does
+     * nothing if there are no properties supplied.
+     * 
+     * @param properties the properties that should be set
+     */
+    void setAllProperties( Property... properties );
+
+    /**
+     * Replace all existing properties with the supplied properties. This method ignores any null property references, and does
+     * nothing if there are no properties supplied.
+     * 
+     * @param properties the properties that should replace the existing properties
+     */
+    void setAllProperties( Iterable<Property> properties );
+
+    /**
+     * Remove all properties, except for the {@link #getName() name} and {@link #getUuid() identifier}.
+     */
+    void removeAllProperties();
+
+    /**
+     * Get the sources that have contributed information to this node.
+     * 
+     * @return the names of the sources that have contributed content to this node.
+     */
+    Set<String> getContributingSources();
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,228 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.io.InvalidClassException;
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+import org.jboss.dna.connector.federation.contribution.EmptyContribution;
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Property;
+
+/**
+ * This class represents the details about how information from different sources are merged into a single federated node.
+ * <p>
+ * A merge plan basically consists of the individual contribution from each source and the information about how these
+ * contributions were merged into the single federated node.
+ * </p>
+ * <p>
+ * Merge plans are designed to be {@link Serializable serializable}, as they are persisted on the federated node and deserialized
+ * to assist in the management of the federated node.
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public abstract class MergePlan implements Serializable, Iterable<Contribution> {
+
+    public static MergePlan create( Contribution... contributions ) {
+        ArgCheck.isNotNull(contributions, "contributions");
+        switch (contributions.length) {
+            case 1:
+                return new SingleContributionMergePlan(contributions[0]);
+            case 2:
+                return new DoubleContributionMergePlan(contributions[0], contributions[1]);
+            case 3:
+                return new TripleContributionMergePlan(contributions[0], contributions[1], contributions[2]);
+            default:
+                return new MultipleContributionMergePlan(contributions);
+        }
+    }
+
+    public static MergePlan addContribution( MergePlan plan,
+                                             Contribution contribution ) {
+        if (plan instanceof MultipleContributionMergePlan) {
+            MultipleContributionMergePlan multiPlan = (MultipleContributionMergePlan)plan;
+            multiPlan.addContribution(contribution);
+            return multiPlan;
+        }
+        if (plan instanceof SingleContributionMergePlan) {
+            MergePlan newPlan = new DoubleContributionMergePlan(plan.iterator().next(), contribution);
+            newPlan.setAnnotations(plan.getAnnotations());
+        }
+        if (plan instanceof DoubleContributionMergePlan) {
+            Iterator<Contribution> iterator = plan.iterator();
+            MergePlan newPlan = new TripleContributionMergePlan(iterator.next(), iterator.next(), contribution);
+            newPlan.setAnnotations(plan.getAnnotations());
+        }
+        MultipleContributionMergePlan newPlan = new MultipleContributionMergePlan();
+        for (Contribution existingContribution : plan) {
+            newPlan.addContribution(existingContribution);
+        }
+        newPlan.addContribution(contribution);
+        newPlan.setAnnotations(plan.getAnnotations());
+        return newPlan;
+    }
+
+    /**
+     * Define the earliest version of this class that is supported. The Java runtime, upon deserialization, compares the
+     * serialized object's version to this, and if less than this version will throw a {@link InvalidClassException}. If, however,
+     * the serialized object's version is compatible with this class, it will be deserialized successfully.
+     * <p>
+     * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/version.html#6678">Sun's documentation</a> describes
+     * the following changes can be made without negatively affecting the deserialization of older versions:
+     * <ul>
+     * <li>Adding fields - When the class being reconstituted has a field that does not occur in the stream, that field in the
+     * object will be initialized to the default value for its type. If class-specific initialization is needed, the class may
+     * provide a readObject method that can initialize the field to nondefault values.</i>
+     * <li>Adding classes - The stream will contain the type hierarchy of each object in the stream. Comparing this hierarchy in
+     * the stream with the current class can detect additional classes. Since there is no information in the stream from which to
+     * initialize the object, the class's fields will be initialized to the default values.</i>
+     * <li>Removing classes - Comparing the class hierarchy in the stream with that of the current class can detect that a class
+     * has been deleted. In this case, the fields and objects corresponding to that class are read from the stream. Primitive
+     * fields are discarded, but the objects referenced by the deleted class are created, since they may be referred to later in
+     * the stream. They will be garbage-collected when the stream is garbage-collected or reset.</i>
+     * <li>Adding writeObject/readObject methods - If the version reading the stream has these methods then readObject is
+     * expected, as usual, to read the required data written to the stream by the default serialization. It should call
+     * defaultReadObject first before reading any optional data. The writeObject method is expected as usual to call
+     * defaultWriteObject to write the required data and then may write optional data.</i>
+     * <li>Removing writeObject/readObject methods - If the class reading the stream does not have these methods, the required
+     * data will be read by default serialization, and the optional data will be discarded.</i>
+     * <li>Adding java.io.Serializable - This is equivalent to adding types. There will be no values in the stream for this class
+     * so its fields will be initialized to default values. The support for subclassing nonserializable classes requires that the
+     * class's supertype have a no-arg constructor and the class itself will be initialized to default values. If the no-arg
+     * constructor is not available, the InvalidClassException is thrown.</i>
+     * <li>Changing the access to a field - The access modifiers public, package, protected, and private have no effect on the
+     * ability of serialization to assign values to the fields.</i>
+     * <li>Changing a field from static to nonstatic or transient to nontransient - When relying on default serialization to
+     * compute the serializable fields, this change is equivalent to adding a field to the class. The new field will be written to
+     * the stream but earlier classes will ignore the value since serialization will not assign values to static or transient
+     * fields.</i>
+     * </ul>
+     * All other kinds of modifications should be avoided.
+     * </p>
+     */
+    private static final long serialVersionUID = 1L;
+
+    private Map<Name, Property> annotations = null;
+    private DateTime expirationTimeInUtc;
+
+    /**
+     * Create an empty merge plan
+     */
+    protected MergePlan() {
+    }
+
+    /**
+     * Determine whether this merge plan has expired given the supplied current time. The {@link #getExpirationTimeInUtc()
+     * expiration time} is the earliest time that any of the {@link #getContributionFrom(String) contributions}
+     * {@link Contribution#getExpirationTimeInUtc()}.
+     * 
+     * @param utcTime the current time expressed in UTC; may not be null
+     * @return true if at least one contribution has expired, or false otherwise
+     */
+    public boolean isExpired( DateTime utcTime ) {
+        assert utcTime != null;
+        assert utcTime.toUtcTimeZone().equals(utcTime); // check that it is passed UTC time
+        return !expirationTimeInUtc.isAfter(utcTime);
+    }
+
+    /**
+     * Get the expiration time (in UTC) that is the earliest time that any of the {@link #getContributionFrom(String)
+     * contributions} {@link Contribution#getExpirationTimeInUtc()}.
+     * 
+     * @return the expiration time in UTC, or null if there is no known expiration time
+     */
+    public DateTime getExpirationTimeInUtc() {
+        return expirationTimeInUtc;
+    }
+
+    /**
+     * Get the contribution from the source with the supplied name. Note that contributions always include sources that contribute
+     * information and sources that contribute no information. If a source is not included in this list, its contributions are
+     * <i>unknown</i>; that is, it is unknown whether that source does or does not contribute to the node.
+     * 
+     * @param sourceName the name of the source
+     * @return the contribution, or null if the contribution of the source is unknown
+     */
+    public abstract Contribution getContributionFrom( String sourceName );
+
+    /**
+     * Return whether the named source was consulted for a contribution.
+     * 
+     * @param sourceName the name of the source
+     * @return true if the source has some {@link Contribution contribution} (even if it is an {@link EmptyContribution})
+     */
+    public abstract boolean isSource( String sourceName );
+
+    public abstract int getContributionCount();
+
+    /**
+     * 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
+     * 
+     * @param name the name of the annotation
+     * @return the existing annotation, or null if there is no annotation with the supplied name
+     * @see #setAnnotation(Property)
+     */
+    public Property getAnnotation( Name name ) {
+        if (name == null) return null;
+        if (this.annotations == null) return null;
+        return this.annotations.get(name);
+    }
+
+    /**
+     * Set the plan annotation property. This method replaces and returns any existing annotation property with the same name.
+     * This method also returns immediately if the supplied annotation is null.
+     * 
+     * @param annotation the new annotation
+     * @return the previous annotation property with the same name, or null if there was no previous annotation property for the
+     *         name
+     * @see #getAnnotation(Name)
+     */
+    public Property setAnnotation( Property annotation ) {
+        if (annotation == null) return null;
+        if (this.annotations == null) {
+            this.annotations = new HashMap<Name, Property>();
+        }
+        return this.annotations.put(annotation.getName(), annotation);
+    }
+
+    protected void setAnnotations( Map<Name, Property> annotations ) {
+        this.annotations = annotations;
+    }
+
+    /**
+     * @return annotations
+     */
+    protected Map<Name, Property> getAnnotations() {
+        return annotations;
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MultipleContributionMergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MultipleContributionMergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/MultipleContributionMergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,116 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class MultipleContributionMergePlan extends MergePlan {
+
+    private static final long serialVersionUID = 1L;
+    private final List<Contribution> contributions;
+
+    /**
+     * @param contributions the contributions for this merge plan
+     */
+    public MultipleContributionMergePlan( Contribution... contributions ) {
+        assert contributions != null;
+        this.contributions = new CopyOnWriteArrayList<Contribution>();
+        for (int i = 0; i != contributions.length; ++i) {
+            assert contributions[i] != null;
+            this.contributions.add(contributions[i]);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionCount()
+     */
+    @Override
+    public int getContributionCount() {
+        return contributions.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionFrom(java.lang.String)
+     */
+    @Override
+    public Contribution getContributionFrom( String sourceName ) {
+        for (Contribution contribution : contributions) {
+            if (contribution.getSourceName().equals(sourceName)) return contribution;
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Iterable#iterator()
+     */
+    public Iterator<Contribution> iterator() {
+        final Iterator<Contribution> iterator = this.contributions.iterator();
+        return new Iterator<Contribution>() {
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+
+            public Contribution next() {
+                return iterator.next();
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#isSource(java.lang.String)
+     */
+    @Override
+    public boolean isSource( String sourceName ) {
+        for (Contribution contribution : contributions) {
+            if (contribution.getSourceName().equals(sourceName)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * @param contribution
+     */
+    public void addContribution( Contribution contribution ) {
+        this.contributions.add(contribution);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SingleContributionMergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SingleContributionMergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/SingleContributionMergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,104 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class SingleContributionMergePlan extends MergePlan {
+
+    private static final long serialVersionUID = 1L;
+    private final Contribution contribution;
+
+    /**
+     * @param contribution the contribution for this merge plan
+     */
+    public SingleContributionMergePlan( Contribution contribution ) {
+        assert contribution != null;
+        this.contribution = contribution;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionCount()
+     */
+    @Override
+    public int getContributionCount() {
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionFrom(java.lang.String)
+     */
+    @Override
+    public Contribution getContributionFrom( String sourceName ) {
+        return isSource(sourceName) ? contribution : null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Iterable#iterator()
+     */
+    public Iterator<Contribution> iterator() {
+        return new Iterator<Contribution>() {
+            private boolean next = true;
+
+            public boolean hasNext() {
+                return next;
+            }
+
+            @SuppressWarnings( "synthetic-access" )
+            public Contribution next() {
+                if (next) {
+                    next = false;
+                    return contribution;
+                }
+                throw new NoSuchElementException();
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#isSource(java.lang.String)
+     */
+    @Override
+    public boolean isSource( String sourceName ) {
+        return contribution.getSourceName().equals(sourceName);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/TripleContributionMergePlan.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/TripleContributionMergePlan.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/java/org/jboss/dna/connector/federation/merge/TripleContributionMergePlan.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,126 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.connector.federation.contribution.Contribution;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class TripleContributionMergePlan extends MergePlan {
+
+    private static final long serialVersionUID = 1L;
+    private final Contribution contribution1;
+    private final Contribution contribution2;
+    private final Contribution contribution3;
+
+    /**
+     * @param contribution1 the first contribution for this merge plan
+     * @param contribution2 the second contribution for this merge plan
+     * @param contribution3 the third contribution for this merge plan
+     */
+    public TripleContributionMergePlan( Contribution contribution1,
+                                        Contribution contribution2,
+                                        Contribution contribution3 ) {
+        assert contribution1 != null;
+        assert contribution2 != null;
+        assert contribution3 != null;
+        this.contribution1 = contribution1;
+        this.contribution2 = contribution2;
+        this.contribution3 = contribution3;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionCount()
+     */
+    @Override
+    public int getContributionCount() {
+        return 3;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#getContributionFrom(java.lang.String)
+     */
+    @Override
+    public Contribution getContributionFrom( String sourceName ) {
+        if (contribution1.getSourceName().equals(sourceName)) return contribution1;
+        if (contribution2.getSourceName().equals(sourceName)) return contribution2;
+        if (contribution3.getSourceName().equals(sourceName)) return contribution3;
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Iterable#iterator()
+     */
+    public Iterator<Contribution> iterator() {
+        return new Iterator<Contribution>() {
+            private int next = 3;
+
+            public boolean hasNext() {
+                return next > 0;
+            }
+
+            @SuppressWarnings( "synthetic-access" )
+            public Contribution next() {
+                if (next == 3) {
+                    next = 2;
+                    return contribution3;
+                }
+                if (next == 2) {
+                    next = 1;
+                    return contribution2;
+                }
+                if (next == 1) {
+                    next = 0;
+                    return contribution1;
+                }
+                throw new NoSuchElementException();
+            }
+
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.connector.federation.merge.MergePlan#isSource(java.lang.String)
+     */
+    @Override
+    public boolean isSource( String sourceName ) {
+        return contribution1.getSourceName().equals(sourceName) || contribution2.getSourceName().equals(sourceName)
+               || contribution3.getSourceName().equals(sourceName);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties
===================================================================
--- trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/main/resources/org/jboss/dna/connector/federation/FederationI18n.properties	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,38 @@
+#
+# JBoss, Home of Professional Open Source.
+# Copyright 2008, Red Hat Middleware LLC, and individual contributors
+# as indicated by the @author tags. See the copyright.txt file in the
+# distribution for a full listing of individual contributors.
+#
+# This is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of
+# the License, or (at your option) any later version.
+#
+# This software is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this software; if not, write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+# 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+#
+requiredNodeDoesNotExistRelativeToNode = The required node {0} does not exist relative to {1}
+propertyIsRequired = The {0} property is required but has no value
+
+interruptedWhileUsingFederationConfigurationRepository = Interrupted while using federation configuration repository "{0}"
+unableToFindFederatedRepositoryInJndi = Unable to find a FederatedRepository instance in JNDI under "{1}" when creating connection to federated source "{0}"
+unableToFindExecutionContextFactoryInJndi = Unable to find an ExecutionContextFactory instance in JNDI under "{1}" when creating connection to federated source "{0}"
+unableToCreateExecutionContext = Unable to create ExecutionContext for connection to federated source "{0}" when using factory in JNDI under "{1}" and security domain "{2}"
+unableToFindRepositoryConnectionFactoriesInJndi = Unable to find an RepositoryConnectionFactories instance in JNDI under "{1}" when creating connection to federated source "{0}"
+noRepositoryConnectionFactories = No RepositoryConnectionFactories instance was specified directly or indirectly found in JNDI when creating connection to federated source "{0}"
+federatedRepositoryCannotBeFound = The federated repository "{0}" cannot be found
+unableToFindRepositorySourceByName = Unable to find the repository source "{0}"
+unableToCreateConnectionToFederatedRepository = Unable to connect to the repository "{0}". Check the Federation Service configuration.
+unableToAuthenticateConnectionToFederatedRepository = Unable to authenticate "{1}" for repository "{0}"
+repositoryHasBeenShutDown = The "{0}" repository has been shut down and may no longer be used.
+repositoryPathInFederationBindingIsNotAbsolute = The repository path in a federation binding must be absolute, but was "{0}"
+errorReadingMergePlan = Error while reading merge plan for {0}
+errorAddingProjectionRuleParseMethod = Error while adding a parsing method for a federation projection rule
\ No newline at end of file

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryConnectionTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryConnectionTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryConnectionTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,132 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import java.util.concurrent.TimeUnit;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.executor.CommandExecutor;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+import org.jboss.dna.spi.graph.connection.RepositorySourceListener;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ * @author Randall Hauch
+ */
+public class FederatedRepositoryConnectionTest {
+
+    private FederatedRepositoryConnection connection;
+    private String sourceName;
+    @Mock
+    private FederatedRepositorySource source;
+    @Mock
+    private FederatedRepository repository;
+
+    @Before
+    public void beforeEach() {
+        MockitoAnnotations.initMocks(this);
+        sourceName = "Source X";
+        stub(source.getName()).toReturn(sourceName);
+        connection = new FederatedRepositoryConnection(repository, sourceName);
+    }
+
+    @Test
+    public void shouldHaveRepository() {
+        assertThat(connection.getRepository(), is(repository));
+    }
+
+    @Test
+    public void shouldHaveNoXaResource() {
+        assertThat(connection.getXAResource(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldCheckRepositoryAdminsStartedStateForPingResult() {
+        stub(repository.isRunning()).toReturn(true);
+        assertThat(connection.ping(1, TimeUnit.SECONDS), is(true));
+        verify(repository, times(1)).isRunning();
+    }
+
+    @Test( expected = RepositorySourceException.class )
+    public void shouldFailExecutionIfRepositoryAdminsIsNotStarted() throws Exception {
+        stub(repository.isRunning()).toReturn(false);
+        ExecutionEnvironment env = mock(ExecutionEnvironment.class);
+        GraphCommand command = mock(GraphCommand.class);
+        connection.execute(env, command);
+    }
+
+    @Test
+    public void shouldReturnImmediatelyWhenExecutingNullOrEmptyCommandArray() throws Exception {
+        stub(repository.isRunning()).toReturn(true);
+        ExecutionEnvironment env = mock(ExecutionEnvironment.class);
+        connection.execute(env, (GraphCommand[])null);
+        verify(repository, times(1)).isRunning();
+        connection.execute(env, new GraphCommand[0]);
+        verify(repository, times(2)).isRunning();
+    }
+
+    @Test
+    public void shouldSkipNullCommandReferencesWhenExecuting() throws Exception {
+        stub(repository.isRunning()).toReturn(true);
+        ExecutionEnvironment env = mock(ExecutionEnvironment.class);
+        CommandExecutor executor = mock(CommandExecutor.class);
+        stub(repository.getExecutor(env, sourceName)).toReturn(executor);
+        connection.execute(env, new GraphCommand[] {null, null, null});
+        verify(repository, times(1)).isRunning();
+    }
+
+    @Test
+    public void shouldAddListenerToRepositoryWhenSetOnConnection() {
+        // Old listener is no-op, so it is not removed from repository ...
+        RepositorySourceListener listener = mock(RepositorySourceListener.class);
+        connection.setListener(listener);
+        verify(repository, times(1)).addListener(listener);
+
+        // Old listener is NOT no-op, so it is removed from repository ...
+        RepositorySourceListener listener2 = mock(RepositorySourceListener.class);
+        connection.setListener(listener2);
+        verify(repository, times(1)).removeListener(listener);
+        verify(repository, times(1)).addListener(listener2);
+    }
+
+    @Test
+    public void shouldRemoveListenerFromRepositoryWhenConnectionIsClosed() {
+        // Old listener is NOT no-op, so it is removed from repository ...
+        RepositorySourceListener listener2 = mock(RepositorySourceListener.class);
+        connection.setListener(listener2);
+        verify(repository, times(1)).addListener(listener2);
+
+        // Closing connection will remove listener ...
+        connection.close();
+        verify(repository, times(1)).removeListener(listener2);
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositorySourceTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositorySourceTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositorySourceTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,309 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import org.jboss.dna.spi.ExecutionContextFactory;
+import org.jboss.dna.spi.graph.connection.BasicExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+import org.jboss.dna.spi.graph.connection.SimpleRepository;
+import org.jboss.dna.spi.graph.connection.SimpleRepositorySource;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ * @author Randall Hauch
+ */
+public class FederatedRepositorySourceTest {
+
+    private FederatedRepositorySource source;
+    private String sourceName;
+    private String repositoryName;
+    private String username;
+    private String credentials;
+    private String executionContextFactoryJndiName;
+    private String connectionFactoriesJndiName;
+    private String configurationSourceName;
+    private String securityDomain;
+    private SimpleRepository configRepository;
+    private SimpleRepositorySource configRepositorySource;
+    private ExecutionEnvironment env;
+    @Mock
+    private FederatedRepositoryConnection connection;
+    @Mock
+    private Context jndiContext;
+    @Mock
+    private RepositoryConnectionFactories connectionFactories;
+    @Mock
+    private ExecutionContextFactory executionContextFactory;
+
+    /**
+     * @throws java.lang.Exception
+     */
+    @Before
+    public void beforeEach() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        env = new BasicExecutionEnvironment();
+        env.getNamespaceRegistry().register("dna", "http://www.jboss.org/dna");
+        executionContextFactoryJndiName = "context factory jndi name";
+        connectionFactoriesJndiName = "connection factories jndi name";
+        configurationSourceName = "configuration source name";
+        repositoryName = "Test Repository";
+        securityDomain = "security domain";
+        source = new FederatedRepositorySource(repositoryName);
+        sourceName = "federated source";
+        username = "valid username";
+        credentials = "valid password";
+        source.setName(sourceName);
+        source.setUsername(username);
+        source.setPassword(credentials);
+        source.setConfigurationSourceName(configurationSourceName);
+        source.setConfigurationSourceProjectionRules(new String[] {"/dna:system/dna:federation/ => /dna:repositories/Test Repository"});
+        source.setConnectionFactoriesJndiName(connectionFactoriesJndiName);
+        source.setExecutionContextFactoryJndiName(executionContextFactoryJndiName);
+        source.setContext(jndiContext);
+        source.setSecurityDomain(securityDomain);
+        configRepository = new SimpleRepository("Configuration Repository");
+        configRepository.setProperty(env, "/dna:repositories/Test Repository", "dna:timeToExpire", "100000");
+        configRepository.setProperty(env, "/dna:repositories/Test Repository", "dna:timeToCache", "100000");
+        configRepository.setProperty(env, "/dna:repositories/Test Repository/dna:cache", "dna:projectionRules", "/ => /");
+        configRepository.setProperty(env,
+                                     "/dna:repositories/Test Repository/dna:projections/source 1/",
+                                     "dna:projectionRules",
+                                     "/ => /s1");
+        configRepository.setProperty(env,
+                                     "/dna:repositories/Test Repository/dna:projections/source 2/",
+                                     "dna:projectionRules",
+                                     "/ => /s1");
+        configRepositorySource = new SimpleRepositorySource();
+        configRepositorySource.setRepositoryName(configRepository.getRepositoryName());
+        configRepositorySource.setName(configurationSourceName);
+        stub(connectionFactories.getConnectionFactory(configurationSourceName)).toReturn(configRepositorySource);
+        stub(jndiContext.lookup(executionContextFactoryJndiName)).toReturn(executionContextFactory);
+        stub(jndiContext.lookup(connectionFactoriesJndiName)).toReturn(connectionFactories);
+        stub(executionContextFactory.create(eq(securityDomain), anyCallbackHandler())).toReturn(env);
+    }
+
+    protected static CallbackHandler anyCallbackHandler() {
+        return argThat(new ArgumentMatcher<CallbackHandler>() {
+            @Override
+            public boolean matches( Object callback ) {
+                return callback != null;
+            }
+        });
+    }
+
+    @After
+    public void afterEach() throws Exception {
+        SimpleRepository.shutdownAll();
+    }
+
+    @Test
+    public void shouldCreateConnectionsByAuthenticateUsingFederationRepository() throws Exception {
+        connection = (FederatedRepositoryConnection)source.getConnection();
+        assertThat(connection, is(sameInstance(connection)));
+    }
+
+    @Test( expected = RepositorySourceException.class )
+    public void shouldNotCreateConnectionWhenAuthenticationFails() throws Exception {
+        // Stub the execution context factory to throw a LoginException to simulate failed authentication
+        stub(executionContextFactory.create(eq(securityDomain), anyCallbackHandler())).toThrow(new LoginException());
+        source.getConnection();
+    }
+
+    @Test( expected = NullPointerException.class )
+    public void shouldPropogateAllExceptionsExceptLoginExceptionThrownFromExecutionContextFactory() throws Exception {
+        // Stub the execution context factory to throw a LoginException to simulate failed authentication
+        stub(executionContextFactory.create(eq(securityDomain), anyCallbackHandler())).toThrow(new NullPointerException());
+        source.getConnection();
+    }
+
+    @Test
+    public void shouldHaveNameSuppliedInConstructor() {
+        source = new FederatedRepositorySource(repositoryName);
+        assertThat(source.getRepositoryName(), is(repositoryName));
+    }
+
+    @Test
+    public void shouldHaveNullSourceNameUponConstruction() {
+        source = new FederatedRepositorySource(repositoryName);
+        assertThat(source.getName(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldAllowSettingName() {
+        source.setName("Something");
+        assertThat(source.getName(), is("Something"));
+        source.setName("another name");
+        assertThat(source.getName(), is("another name"));
+    }
+
+    @Test
+    public void shouldAllowSettingNameToNull() {
+        source.setName("some name");
+        source.setName(null);
+        assertThat(source.getName(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldAllowSettingUsername() {
+        source.setUsername("Something");
+        assertThat(source.getUsername(), is("Something"));
+        source.setUsername("another name");
+        assertThat(source.getUsername(), is("another name"));
+    }
+
+    @Test
+    public void shouldAllowSettingUsernameToNull() {
+        source.setUsername("some name");
+        source.setUsername(null);
+        assertThat(source.getUsername(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldAllowSettingCredentials() {
+        source.setPassword("Something");
+        assertThat(source.getPassword(), is("Something"));
+        source.setPassword("another name");
+        assertThat(source.getPassword(), is("another name"));
+    }
+
+    @Test
+    public void shouldAllowSettingCredentialsToNull() {
+        source.setPassword("some name");
+        source.setPassword(null);
+        assertThat(source.getPassword(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldHaveDefaultRetryLimit() {
+        assertThat(source.getRetryLimit(), is(FederatedRepositorySource.DEFAULT_RETRY_LIMIT));
+    }
+
+    @Test
+    public void shouldSetRetryLimitToZeroWhenSetWithNonPositiveValue() {
+        source.setRetryLimit(0);
+        assertThat(source.getRetryLimit(), is(0));
+        source.setRetryLimit(-1);
+        assertThat(source.getRetryLimit(), is(0));
+        source.setRetryLimit(-100);
+        assertThat(source.getRetryLimit(), is(0));
+    }
+
+    @Test
+    public void shouldAllowRetryLimitToBeSet() {
+        for (int i = 0; i != 100; ++i) {
+            source.setRetryLimit(i);
+            assertThat(source.getRetryLimit(), is(i));
+        }
+    }
+
+    @Test
+    public void shouldCreateJndiReferenceAndRecreatedObjectFromReference() throws Exception {
+        int retryLimit = 100;
+        source.setPassword(credentials);
+        source.setUsername(username);
+        source.setRetryLimit(retryLimit);
+        source.setName("Some source");
+        source.setConfigurationSourceName("config source");
+        source.setConfigurationSourceProjectionRules(new String[] {"/dna:system => /a/b/c"});
+        source.setConnectionFactoriesJndiName("connection factories jndi name");
+        source.setRepositoryJndiName("repository jndi name");
+        source.setExecutionContextFactoryJndiName("env jndi name");
+
+        Reference ref = source.getReference();
+        assertThat(ref.getClassName(), is(FederatedRepositorySource.class.getName()));
+        assertThat(ref.getFactoryClassName(), is(FederatedRepositorySource.NamingContextObjectFactory.class.getName()));
+
+        Map<String, Object> refAttributes = new HashMap<String, Object>();
+        Enumeration<RefAddr> enumeration = ref.getAll();
+        while (enumeration.hasMoreElements()) {
+            RefAddr addr = enumeration.nextElement();
+            refAttributes.put(addr.getType(), addr.getContent());
+        }
+
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.USERNAME), is(username));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.PASSWORD), is(credentials));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.SOURCE_NAME), is(source.getName()));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.REPOSITORY_NAME), is(repositoryName));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.RETRY_LIMIT), is(Integer.toString(retryLimit)));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.CONFIGURATION_SOURCE_NAME),
+                   is(source.getConfigurationSourceName()));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.CONFIGURATION_SOURCE_PROJECTION_RULES),
+                   is("/dna:system => /a/b/c"));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.CONNECTION_FACTORIES_JNDI_NAME),
+                   is(source.getConnectionFactoriesJndiName()));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.EXECUTION_CONTEXT_FACTORY_JNDI_NAME),
+                   is(source.getExecutionContextFactoryJndiName()));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.REPOSITORY_JNDI_NAME),
+                   is(source.getRepositoryJndiName()));
+        assertThat((String)refAttributes.remove(FederatedRepositorySource.SECURITY_DOMAIN), is(securityDomain));
+        assertThat(refAttributes.isEmpty(), is(true));
+
+        // Recreate the object, use a newly constructed source ...
+        FederatedRepositorySource.NamingContextObjectFactory factory = new FederatedRepositorySource.NamingContextObjectFactory();
+        Name name = mock(Name.class);
+        Context context = mock(Context.class);
+        Hashtable<?, ?> env = new Hashtable<Object, Object>();
+        FederatedRepositorySource recoveredSource = (FederatedRepositorySource)factory.getObjectInstance(ref, name, context, env);
+        assertThat(recoveredSource, is(notNullValue()));
+
+        assertThat(recoveredSource.getName(), is(source.getName()));
+        assertThat(recoveredSource.getUsername(), is(source.getUsername()));
+        assertThat(recoveredSource.getPassword(), is(source.getPassword()));
+        assertThat(recoveredSource.getRepositoryName(), is(source.getRepositoryName()));
+        assertThat(recoveredSource.getRetryLimit(), is(source.getRetryLimit()));
+        assertThat(recoveredSource.getConfigurationSourceName(), is(source.getConfigurationSourceName()));
+        assertThat(recoveredSource.getConfigurationSourceProjectionRules(), is(source.getConfigurationSourceProjectionRules()));
+        assertThat(recoveredSource.getConnectionFactoriesJndiName(), is(source.getConnectionFactoriesJndiName()));
+        assertThat(recoveredSource.getExecutionContextFactoryJndiName(), is(source.getExecutionContextFactoryJndiName()));
+        assertThat(recoveredSource.getRepositoryJndiName(), is(source.getRepositoryJndiName()));
+        assertThat(recoveredSource.getSecurityDomain(), is(source.getSecurityDomain()));
+
+        assertThat(recoveredSource.equals(source), is(true));
+        assertThat(source.equals(recoveredSource), is(true));
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederatedRepositoryTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,146 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import org.jboss.dna.spi.graph.connection.BasicExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositorySourceListener;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ * @author Randall Hauch
+ */
+public class FederatedRepositoryTest {
+
+    private ExecutionEnvironment env;
+    private FederatedRepository repository;
+    @Mock
+    private FederatedRepositoryConfig config;
+    @Mock
+    private RepositorySourceListener listener1;
+    @Mock
+    private RepositorySourceListener listener2;
+    @Mock
+    private RepositoryConnectionFactories connectionFactories;
+
+    // private BasicRepositoryConnectionPool connectionPool;
+
+    @Before
+    public void beforeEach() {
+        MockitoAnnotations.initMocks(this);
+        env = new BasicExecutionEnvironment();
+        repository = new FederatedRepository(env, connectionFactories, config);
+    }
+
+    @Test
+    public void shouldHaveNoNameUponCreation() {
+        assertThat(repository.getName(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldHaveNoListenersUponCreation() {
+        assertThat(repository.getListeners(), is(notNullValue()));
+        assertThat(repository.getListeners().isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldNotAddNullListener() {
+        assertThat(repository.addListener(null), is(false));
+    }
+
+    @Test
+    public void shouldNotAddListenerIfAlreadyInList() {
+        assertThat(repository.getListeners().size(), is(0));
+        assertThat(repository.addListener(listener1), is(true));
+        assertThat(repository.getListeners().size(), is(1));
+        assertThat(repository.addListener(listener1), is(false));
+        assertThat(repository.getListeners().size(), is(1));
+    }
+
+    @Test
+    public void shouldAddDifferentListeners() {
+        assertThat(repository.getListeners().size(), is(0));
+        assertThat(repository.addListener(listener1), is(true));
+        assertThat(repository.getListeners().size(), is(1));
+        assertThat(repository.addListener(listener2), is(true));
+        assertThat(repository.getListeners().size(), is(2));
+        assertThat(repository.getListeners(), hasItems(listener1, listener2));
+        assertThat(repository.getListeners().get(0), is(sameInstance(listener1)));
+        assertThat(repository.getListeners().get(1), is(sameInstance(listener2)));
+    }
+
+    @Test
+    public void shouldAllowReorderingOfListeners() {
+        assertThat(repository.getListeners().size(), is(0));
+        assertThat(repository.addListener(listener1), is(true));
+        assertThat(repository.addListener(listener2), is(true));
+        assertThat(repository.getListeners().size(), is(2));
+        assertThat(repository.getListeners(), hasItems(listener1, listener2));
+        repository.getListeners().remove(0);
+        repository.getListeners().add(1, listener1);
+        assertThat(repository.getListeners(), hasItems(listener2, listener1));
+        assertThat(repository.getListeners().get(0), is(sameInstance(listener2)));
+        assertThat(repository.getListeners().get(1), is(sameInstance(listener1)));
+    }
+
+    @Test
+    public void shouldAllowRemovalOfListeners() {
+        assertThat(repository.getListeners().size(), is(0));
+        assertThat(repository.addListener(listener1), is(true));
+        assertThat(repository.addListener(listener2), is(true));
+        assertThat(repository.getListeners(), hasItems(listener1, listener2));
+        assertThat(repository.removeListener(listener1), is(true));
+        assertThat(repository.getListeners(), hasItems(listener2));
+        assertThat(repository.removeListener(listener2), is(true));
+        assertThat(repository.getListeners(), hasItems(new RepositorySourceListener[] {}));
+    }
+
+    @Test
+    public void shouldNotRemoveListenerThatIsNotAlreadyRegistered() {
+        assertThat(repository.getListeners().size(), is(0));
+        assertThat(repository.addListener(listener1), is(true));
+        assertThat(repository.getListeners().size(), is(1));
+        assertThat(repository.removeListener(listener2), is(false));
+    }
+
+    @Test
+    public void shouldHaveConfigurationAfterInitialization() {
+        assertThat(repository.getConfiguration(), is(notNullValue()));
+        assertThat(repository.getConfiguration(), is(sameInstance(config)));
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldNotAllowSettingConfigurationToNull() {
+        repository.setConfiguration(null);
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederationI18nTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederationI18nTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/FederationI18nTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,34 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import org.jboss.dna.common.AbstractI18nTest;
+
+/**
+ * @author Randall Hauch
+ */
+public class FederationI18nTest extends AbstractI18nTest {
+
+    public FederationI18nTest() {
+        super(FederationI18n.class);
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionParserTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionParserTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionParserTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectionParserTest {
+
+    private ProjectionParser parser;
+
+    @Test
+    public void shouldInitializeSingletonWithParserMethods() {
+        parser = ProjectionParser.getInstance();
+        assertThat(parser.getParserMethods().size(), is(1));
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionPathRuleTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionPathRuleTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionPathRuleTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,249 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.text.UrlEncoder;
+import org.jboss.dna.spi.graph.NamespaceRegistry;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.connection.BasicExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectionPathRuleTest {
+
+    private ExecutionEnvironment env;
+    private Projection.PathRule rule;
+    private PathFactory pathFactory;
+    private Path repositoryPath;
+    private Path sourcePath;
+    private Path[] validExceptions;
+    private NamespaceRegistry registry;
+    private TextEncoder encoder;
+
+    @Before
+    public void beforeEach() {
+        env = new BasicExecutionEnvironment();
+        pathFactory = env.getValueFactories().getPathFactory();
+        registry = env.getNamespaceRegistry();
+        encoder = new UrlEncoder();
+        repositoryPath = pathFactory.create("/a/b/c");
+        sourcePath = pathFactory.create("/x/y");
+        validExceptions = new Path[] {pathFactory.create("e/f"), pathFactory.create("e/g")};
+        rule = new Projection.PathRule(repositoryPath, sourcePath, validExceptions);
+    }
+
+    @Test
+    public void shouldCreateInstanceWithValidRepositoryPathAndValidSourcePathAndNoExceptions() {
+        rule = new Projection.PathRule(repositoryPath, sourcePath);
+        assertThat(rule.getPathInRepository(), is(sameInstance(repositoryPath)));
+        assertThat(rule.getPathInSource(), is(sameInstance(sourcePath)));
+        assertThat(rule.hasExceptionsToRule(), is(false));
+    }
+
+    @Test
+    public void shouldCreateInstanceWithValidRepositoryPathAndValidSourcePathAndValidExceptions() {
+        rule = new Projection.PathRule(repositoryPath, sourcePath, validExceptions);
+        assertThat(rule.getPathInRepository(), is(sameInstance(repositoryPath)));
+        assertThat(rule.getPathInSource(), is(sameInstance(sourcePath)));
+        assertThat(rule.hasExceptionsToRule(), is(true));
+        assertThat(rule.getExceptionsToRule(), hasItems(validExceptions));
+    }
+
+    @Test( expected = AssertionError.class )
+    public void shouldFailToCreateInstanceWithNullRepositoryPathAndValidSourcePathAndNoExceptions() {
+        repositoryPath = null;
+        new Projection.PathRule(repositoryPath, sourcePath);
+    }
+
+    @Test( expected = AssertionError.class )
+    public void shouldFailToCreateInstanceWithValidRepositoryPathAndNullSourcePathAndNoExceptions() {
+        sourcePath = null;
+        new Projection.PathRule(repositoryPath, sourcePath);
+    }
+
+    @Test( expected = AssertionError.class )
+    public void shouldFailToCreateInstanceWithValidRepositoryPathAndValidSourcePathAndAbsoluteExceptions() {
+        Path relativePath = validExceptions[0];
+        Path absolutePath = pathFactory.create("/j/k/l/m");
+        new Projection.PathRule(repositoryPath, sourcePath, relativePath, absolutePath);
+    }
+
+    @Test
+    public void shouldIncludeRepositoryPathsAtPathInRepository() {
+        assertThat(rule.includes(sourcePath), is(true));
+    }
+
+    @Test
+    public void shouldIncludeRepositoryPathsBelowPathInRepositoryThatAreNotExcluded() {
+        assertThat(rule.includes(pathFactory.create(sourcePath, "m")), is(true));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "m/n")), is(true));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "o/p")), is(true));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e/e")), is(true));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e")), is(true));
+    }
+
+    @Test
+    public void shouldNotIncludeRepositoryPathsBelowPathInRepositoryThatAreExcluded() {
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e/f")), is(false));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e/g")), is(false));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e/f/g")), is(false));
+        assertThat(rule.includes(pathFactory.create(sourcePath, "e/g/h")), is(false));
+    }
+
+    @Test
+    public void shouldNotIncludeRepositoryPathsNotBelowPathInRepository() {
+        assertThat(rule.includes(pathFactory.create("/m/n")), is(false));
+        assertThat(rule.includes(pathFactory.create("/x/y[3]")), is(false));
+    }
+
+    @Test
+    public void shouldProjectRepositoryPathIntoSourcePath() {
+        assertThat(rule.projectPathInRepositoryToPathInSource(repositoryPath, pathFactory), is(sourcePath));
+    }
+
+    @Test
+    public void shouldProjectPathBelowRepositoryPathIntoPathBelowSourcePath() {
+        Path pathInRepository = pathFactory.create(repositoryPath, "m/n");
+        Path pathInSource = pathFactory.create(sourcePath, "m/n");
+        assertThat(rule.projectPathInRepositoryToPathInSource(pathInRepository, pathFactory), is(pathInSource));
+
+        pathInRepository = pathFactory.create(repositoryPath, "m");
+        pathInSource = pathFactory.create(sourcePath, "m");
+        assertThat(rule.projectPathInRepositoryToPathInSource(pathInRepository, pathFactory), is(pathInSource));
+
+        pathInRepository = pathFactory.create(repositoryPath, "m/n[3]");
+        pathInSource = pathFactory.create(sourcePath, "m/n[3]");
+        assertThat(rule.projectPathInRepositoryToPathInSource(pathInRepository, pathFactory), is(pathInSource));
+    }
+
+    @Test
+    public void shouldProjectSourcePathIntoRepositoryPath() {
+        assertThat(rule.projectPathInSourceToPathInRepository(sourcePath, pathFactory), is(repositoryPath));
+    }
+
+    @Test
+    public void shouldProjectPathBelowSourcePathIntoPathBelowRepositoryPath() {
+        Path pathInRepository = pathFactory.create(repositoryPath, "m/n");
+        Path pathInSource = pathFactory.create(sourcePath, "m/n");
+        assertThat(rule.projectPathInSourceToPathInRepository(pathInSource, pathFactory), is(pathInRepository));
+
+        pathInRepository = pathFactory.create(repositoryPath, "m");
+        pathInSource = pathFactory.create(sourcePath, "m");
+        assertThat(rule.projectPathInSourceToPathInRepository(pathInSource, pathFactory), is(pathInRepository));
+
+        pathInRepository = pathFactory.create(repositoryPath, "m/n[3]");
+        pathInSource = pathFactory.create(sourcePath, "m/n[3]");
+        assertThat(rule.projectPathInSourceToPathInRepository(pathInSource, pathFactory), is(pathInRepository));
+    }
+
+    @Test
+    public void shouldGetPathsInRepositoryGivenPathsInSourceAtOrBelowSourcePathIfNotExcluded() {
+        assertThat(rule.getPathInRepository(sourcePath, pathFactory), is(repositoryPath));
+        assertThatGetPathInRepositoryReturnsCorrectPathInSource("");
+        assertThatGetPathInRepositoryReturnsCorrectPathInSource("m/n");
+        assertThatGetPathInRepositoryReturnsCorrectPathInSource("m[0]");
+        assertThatGetPathInRepositoryReturnsCorrectPathInSource("m[0]/n/o/p");
+    }
+
+    protected void assertThatGetPathInRepositoryReturnsCorrectPathInSource( String subpath ) {
+        assertThat(rule.getPathInRepository(pathFactory.create(sourcePath, subpath), pathFactory),
+                   is(pathFactory.create(repositoryPath, subpath)));
+    }
+
+    @Test
+    public void shouldGetNullPathInRepositoryGivenPathsInSourceAtOrBelowSourcePathIfExcluded() {
+        assertThat(rule.getPathInRepository(pathFactory.create(sourcePath, "e/f"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInRepository(pathFactory.create(sourcePath, "e/g"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInRepository(pathFactory.create(sourcePath, "e/f/h"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInRepository(pathFactory.create(sourcePath, "e/g/h"), pathFactory), is(nullValue()));
+    }
+
+    @Test
+    public void shouldGetNullPathInRepositoryGivenPathsInRepositoryNotAtOrBelowSourcePath() {
+        assertThat(rule.getPathInRepository(pathFactory.create("/m/n"), pathFactory), is(nullValue()));
+    }
+
+    @Test
+    public void shouldGetPathsInSourceGivenPathsInRepositoryAtOrBelowRepositoryPathIfNotExcluded() {
+        assertThat(rule.getPathInSource(repositoryPath, pathFactory), is(sourcePath));
+        assertThatGetPathInSourceReturnsCorrectPathInRepository("");
+        assertThatGetPathInSourceReturnsCorrectPathInRepository("m/n");
+        assertThatGetPathInSourceReturnsCorrectPathInRepository("m[0]");
+        assertThatGetPathInSourceReturnsCorrectPathInRepository("m[0]/n/o/p");
+
+    }
+
+    protected void assertThatGetPathInSourceReturnsCorrectPathInRepository( String subpath ) {
+        assertThat(rule.getPathInSource(pathFactory.create(repositoryPath, subpath), pathFactory),
+                   is(pathFactory.create(sourcePath, subpath)));
+    }
+
+    @Test
+    public void shouldGetNullPathInSourceGivenPathsInRepositoryAtOrBelowRepositoryPathIfExcluded() {
+        assertThat(rule.getPathInSource(pathFactory.create(repositoryPath, "e/f"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInSource(pathFactory.create(repositoryPath, "e/g"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInSource(pathFactory.create(repositoryPath, "e/f/h"), pathFactory), is(nullValue()));
+        assertThat(rule.getPathInSource(pathFactory.create(repositoryPath, "e/g/h"), pathFactory), is(nullValue()));
+    }
+
+    @Test
+    public void shouldGetNullPathInSourceGivenPathsInRepositoryNotAtOrBelowRepositoryPath() {
+        assertThat(rule.getPathInSource(pathFactory.create("/m/n"), pathFactory), is(nullValue()));
+    }
+
+    @Test
+    public void shouldConvertToString() {
+        assertThat(rule.getString(registry, encoder), is("/a/b/c => /x/y $ e/f $ e/g"));
+
+        repositoryPath = pathFactory.create("/a/b/c");
+        sourcePath = pathFactory.create("/");
+        rule = new Projection.PathRule(repositoryPath, sourcePath, validExceptions);
+        assertThat(rule.getString(registry, encoder), is("/a/b/c => / $ e/f $ e/g"));
+
+        repositoryPath = pathFactory.create("/");
+        sourcePath = pathFactory.create("/");
+        rule = new Projection.PathRule(repositoryPath, sourcePath, validExceptions);
+        assertThat(rule.getString(registry, encoder), is("/ => / $ e/f $ e/g"));
+    }
+
+    @Test
+    public void shouldHaveToString() {
+        assertThat(rule.toString(), is("/{}a/{}b/{}c => /{}x/{}y $ {}e/{}f $ {}e/{}g"));
+
+        repositoryPath = pathFactory.create("/a/b/c");
+        sourcePath = pathFactory.create("/");
+        rule = new Projection.PathRule(repositoryPath, sourcePath, validExceptions);
+        assertThat(rule.toString(), is("/{}a/{}b/{}c => / $ {}e/{}f $ {}e/{}g"));
+    }
+
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/ProjectionTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,231 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.mockito.Mockito.stub;
+import java.util.Set;
+import org.jboss.dna.connector.federation.Projection;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.connection.BasicExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ * @author Randall Hauch
+ */
+public class ProjectionTest {
+
+    private ExecutionEnvironment env;
+    private String sourceName;
+    private Projection.Rule[] rules;
+    private Projection projection;
+    private PathFactory pathFactory;
+    @Mock
+    private Projection.Rule mockRule1;
+    @Mock
+    private Projection.Rule mockRule2;
+    @Mock
+    private Projection.Rule mockRule3;
+
+    @Before
+    public void beforeEach() {
+        MockitoAnnotations.initMocks(this);
+        env = new BasicExecutionEnvironment();
+        pathFactory = env.getValueFactories().getPathFactory();
+        sourceName = "Valid name";
+        rules = new Projection.Rule[] {mockRule1, mockRule2, mockRule3};
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test
+    public void shouldCreateInstanceWithValidNameAndValidRules() {
+        projection = new Projection(sourceName, rules);
+        assertThat(projection.getSourceName(), is(sourceName));
+        assertThat(projection.getRules().size(), is(rules.length));
+        assertThat(projection.getRules(), hasItems(mockRule1, mockRule2, mockRule3));
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithNullNameAndValidRules() {
+        sourceName = null;
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithEmptyNameAndValidRules() {
+        sourceName = "";
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithBlankNameAndValidRules() {
+        sourceName = "   \t ";
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithValidNameAndNullRules() {
+        rules = null;
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithValidNameAndEmptyRules() {
+        rules = new Projection.Rule[] {};
+        projection = new Projection(sourceName, rules);
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToCreateInstanceWithValidNameAndRulesArrayContainingAllNulls() {
+        projection = new Projection(sourceName, null, null, null);
+    }
+
+    @Test
+    public void shouldCreateInstanceWithValidNameAndRulesAndShouldPruneNullRuleReferences() {
+        projection = new Projection(sourceName, mockRule1, null, mockRule3);
+        assertThat(projection.getRules().size(), is(2));
+        assertThat(projection.getRules(), hasItems(mockRule1, mockRule3));
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToGetPathsInSourceGivenValidPathAndNullPathFactory() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        projection.getPathsInSource(pathInRepository, null);
+    }
+
+    @Test
+    public void shouldGetNoPathsInSourceGivenNullPathInRepository() {
+        Set<Path> pathsInSource = projection.getPathsInSource(null, pathFactory);
+        assertThat(pathsInSource.isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldGetNoPathsInSourceGivenPathInRepositoryAndNoApplicableRules() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        stub(mockRule1.getPathInSource(pathInRepository, pathFactory)).toReturn(null);
+        stub(mockRule2.getPathInSource(pathInRepository, pathFactory)).toReturn(null);
+        stub(mockRule3.getPathInSource(pathInRepository, pathFactory)).toReturn(null);
+        Set<Path> pathsInSource = projection.getPathsInSource(pathInRepository, pathFactory);
+        assertThat(pathsInSource.isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldGetPathInSourceGivenPathInRepositoryAndOneApplicableRules() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        Path pathInSource = pathFactory.create("/d/e/f");
+        stub(mockRule1.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource);
+        stub(mockRule2.getPathInSource(pathInRepository, pathFactory)).toReturn(null);
+        stub(mockRule3.getPathInSource(pathInRepository, pathFactory)).toReturn(null);
+        Set<Path> pathsInSource = projection.getPathsInSource(pathInRepository, pathFactory);
+        assertThat(pathsInSource, hasItems(pathInSource));
+    }
+
+    @Test
+    public void shouldGetPathsInSourceGivenPathInRepositoryAndMultipleApplicableRules() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        Path pathInSource1 = pathFactory.create("/d/e/f");
+        Path pathInSource2 = pathFactory.create("/d/e/g");
+        Path pathInSource3 = pathFactory.create("/d/e/h");
+        stub(mockRule1.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource1);
+        stub(mockRule2.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource2);
+        stub(mockRule3.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource3);
+        Set<Path> pathsInSource = projection.getPathsInSource(pathInRepository, pathFactory);
+        assertThat(pathsInSource, hasItems(pathInSource1, pathInSource2, pathInSource3));
+    }
+
+    @Test
+    public void shouldGetPathsInSourceGivenPathInRepositoryAndMultipleApplicableRulesReturningDuplicatePathsInSource() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        Path pathInSource1 = pathFactory.create("/d/e/f");
+        Path pathInSource23 = pathFactory.create("/d/e/g");
+        stub(mockRule1.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource1);
+        stub(mockRule2.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource23);
+        stub(mockRule3.getPathInSource(pathInRepository, pathFactory)).toReturn(pathInSource23);
+        Set<Path> pathsInSource = projection.getPathsInSource(pathInRepository, pathFactory);
+        assertThat(pathsInSource, hasItems(pathInSource1, pathInSource23));
+    }
+
+    @Test( expected = IllegalArgumentException.class )
+    public void shouldFailToGetPathsInRepositoryGivenValidPathAndNullPathFactory() {
+        Path pathInSource = pathFactory.create("/a/b/c");
+        projection.getPathsInRepository(pathInSource, null);
+    }
+
+    @Test
+    public void shouldGetNoPathsInRepositoryGivenNullPathInSource() {
+        Set<Path> pathsInRepository = projection.getPathsInRepository(null, pathFactory);
+        assertThat(pathsInRepository.isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldGetNoPathsInRepositoryGivenPathInSourceAndNoApplicableRules() {
+        Path pathInSource = pathFactory.create("/d/e/f");
+        stub(mockRule1.getPathInRepository(pathInSource, pathFactory)).toReturn(null);
+        stub(mockRule2.getPathInRepository(pathInSource, pathFactory)).toReturn(null);
+        stub(mockRule3.getPathInRepository(pathInSource, pathFactory)).toReturn(null);
+        Set<Path> pathsInRepository = projection.getPathsInRepository(pathInSource, pathFactory);
+        assertThat(pathsInRepository.isEmpty(), is(true));
+    }
+
+    @Test
+    public void shouldGetPathInRepositoryGivenPathInSourceAndOneApplicableRules() {
+        Path pathInRepository = pathFactory.create("/a/b/c");
+        Path pathInSource = pathFactory.create("/d/e/f");
+        stub(mockRule1.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository);
+        stub(mockRule2.getPathInRepository(pathInSource, pathFactory)).toReturn(null);
+        stub(mockRule3.getPathInRepository(pathInSource, pathFactory)).toReturn(null);
+        Set<Path> pathsInRepository = projection.getPathsInRepository(pathInSource, pathFactory);
+        assertThat(pathsInRepository, hasItems(pathInRepository));
+    }
+
+    @Test
+    public void shouldGetPathsInRepositoryGivenPathInSourceAndMultipleApplicableRules() {
+        Path pathInSource = pathFactory.create("/a/b/c");
+        Path pathInRepository1 = pathFactory.create("/d/e/f");
+        Path pathInRepository2 = pathFactory.create("/d/e/g");
+        Path pathInRepository3 = pathFactory.create("/d/e/h");
+        stub(mockRule1.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository1);
+        stub(mockRule2.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository2);
+        stub(mockRule3.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository3);
+        Set<Path> pathsInRepository = projection.getPathsInRepository(pathInSource, pathFactory);
+        assertThat(pathsInRepository, hasItems(pathInRepository1, pathInRepository2, pathInRepository3));
+    }
+
+    @Test
+    public void shouldGetPathsInRepositoryGivenPathInSourceAndMultipleApplicableRulesReturningDuplicatePathsInRepository() {
+        Path pathInSource = pathFactory.create("/a/b/c");
+        Path pathInRepository1 = pathFactory.create("/d/e/f");
+        Path pathInRepository23 = pathFactory.create("/d/e/g");
+        stub(mockRule1.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository1);
+        stub(mockRule2.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository23);
+        stub(mockRule3.getPathInRepository(pathInSource, pathFactory)).toReturn(pathInRepository23);
+        Set<Path> pathsInRepository = projection.getPathsInRepository(pathInSource, pathFactory);
+        assertThat(pathsInRepository, hasItems(pathInRepository1, pathInRepository23));
+    }
+}

Added: trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/BasicFederatedNodeTest.java
===================================================================
--- trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/BasicFederatedNodeTest.java	                        (rev 0)
+++ trunk/connectors/dna-connector-federation/src/test/java/org/jboss/dna/connector/federation/merge/BasicFederatedNodeTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.connector.federation.merge;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class BasicFederatedNodeTest {
+
+    @Before
+    public void beforeEach() {
+    }
+
+    @Test
+    public void shouldDoSomething() {
+
+    }
+
+}

Modified: trunk/dna-repository/pom.xml
===================================================================
--- trunk/dna-repository/pom.xml	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-repository/pom.xml	2008-07-24 19:52:13 UTC (rev 369)
@@ -43,6 +43,17 @@
       <type>test-jar</type>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-connector-federation</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.dna</groupId>
+      <artifactId>dna-connector-federation</artifactId>
+      <version>${dna-version}</version>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
     <!-- 
     Rules
     -->

Modified: trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -41,6 +41,7 @@
     public static I18n unableToUnregisterRepositoryInJndi;
     public static I18n unableToRemoveRepository;
     public static I18n unableToFindRepositoryWithName;
+    public static I18n unableToFindRepositorySourceWithName;
     public static I18n errorProcessingEvents;
     public static I18n errorFindingPropertyNameInPropertyAddedEvent;
     public static I18n errorFindingPropertyNameInPropertyChangedEvent;
@@ -122,17 +123,7 @@
     public static I18n warningSequencingXmlDocument;
 
     // Federation
-    public static I18n interruptedWhileConnectingToFederationConfigurationRepository;
-    public static I18n interruptedWhileUsingFederationConfigurationRepository;
-    public static I18n interruptedWhileClosingConnectionToFederationConfigurationRepository;
-    public static I18n federatedRepositoryCannotBeFound;
-    public static I18n unableToConnectToFederationConfigurationRepository;
-    public static I18n unableToFindRepositorySourceByName;
-    public static I18n unableToCreateConnectionToFederatedRepository;
-    public static I18n unableToAuthenticateConnectionToFederatedRepository;
-    public static I18n repositoryHasBeenShutDown;
-    public static I18n repositoryPathInFederationBindingIsNotAbsolute;
-    public static I18n errorReadingMergePlan;
+    public static I18n errorStartingRepositoryService;
 
     static {
         try {

Copied: trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryService.java (from rev 346, trunk/dna-repository/src/main/java/org/jboss/dna/repository/federation/FederationService.java)
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryService.java	                        (rev 0)
+++ trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryService.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,368 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.repository;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.collection.SimpleProblems;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.component.StandardClassLoaderFactory;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Reflection;
+import org.jboss.dna.connector.federation.FederatedRepositorySource;
+import org.jboss.dna.connector.federation.FederationException;
+import org.jboss.dna.connector.federation.Projection;
+import org.jboss.dna.connector.federation.executor.FederatingCommandExecutor;
+import org.jboss.dna.connector.federation.executor.SingleProjectionCommandExecutor;
+import org.jboss.dna.repository.services.AbstractServiceAdministrator;
+import org.jboss.dna.repository.services.AdministeredService;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.NameFactory;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.ValueFactories;
+import org.jboss.dna.spi.graph.ValueFactory;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.executor.CommandExecutor;
+import org.jboss.dna.spi.graph.commands.executor.NoOpCommandExecutor;
+import org.jboss.dna.spi.graph.commands.impl.BasicCompositeCommand;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.impl.BasicGetNodeCommand;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+
+/**
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class RepositoryService implements AdministeredService {
+
+    protected static final String CLASSNAME_PROPERTY_NAME = "dna:classname";
+    protected static final String CLASSPATH_PROPERTY_NAME = "dna:classpath";
+    protected static final String PROJECTION_RULES_PROPERTY_NAME = "dna:projectionRules";
+    protected static final String CACHE_POLICY_TIME_TO_EXPIRE = "dna:timeToExpire";
+    protected static final String CACHE_POLICY_TIME_TO_CACHE = "dna:timeToCache";
+
+    /**
+     * The administrative component for this service.
+     * 
+     * @author Randall Hauch
+     */
+    protected class Administrator extends AbstractServiceAdministrator {
+
+        protected Administrator() {
+            super(RepositoryI18n.federationServiceName, State.PAUSED);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected boolean doCheckIsTerminated() {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected void doStart( State fromState ) {
+            super.doStart(fromState);
+            startService();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.repository.services.ServiceAdministrator#awaitTermination(long, java.util.concurrent.TimeUnit)
+         */
+        public boolean awaitTermination( long timeout,
+                                         TimeUnit unit ) {
+            return true;
+        }
+    }
+
+    private final ClassLoaderFactory classLoaderFactory;
+    private final ExecutionEnvironment env;
+    private final RepositorySourceManager sources;
+    private final Projection configurationProjection;
+    private final Administrator administrator = new Administrator();
+    private final AtomicBoolean started = new AtomicBoolean(false);
+
+    /**
+     * Create a federation service instance
+     * 
+     * @param sources the source manager
+     * @param configurationProjection the projection defining where the service can find configuration information for the
+     *        different repositories that it is to manage
+     * @param env the execution environment in which this service should run
+     * @param classLoaderFactory the class loader factory used to instantiate {@link RepositorySource} instances; may be null if
+     *        this instance should use a default factory that attempts to load classes first from the
+     *        {@link Thread#getContextClassLoader() thread's current context class loader} and then from the class loader that
+     *        loaded this class.
+     * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null
+     */
+    public RepositoryService( RepositorySourceManager sources,
+                              Projection configurationProjection,
+                              ExecutionEnvironment env,
+                              ClassLoaderFactory classLoaderFactory ) {
+        ArgCheck.isNotNull(configurationProjection, "configurationProjection");
+        ArgCheck.isNotNull(sources, "sources");
+        ArgCheck.isNotNull(env, "env");
+        this.sources = sources;
+        this.configurationProjection = configurationProjection;
+        this.env = env;
+        this.classLoaderFactory = classLoaderFactory != null ? classLoaderFactory : new StandardClassLoaderFactory();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ServiceAdministrator getAdministrator() {
+        return this.administrator;
+    }
+
+    /**
+     * @return configurationProjection
+     */
+    public Projection getConfigurationProjection() {
+        return configurationProjection;
+    }
+
+    /**
+     * @return sources
+     */
+    public RepositorySourceManager getRepositorySourceManager() {
+        return sources;
+    }
+
+    /**
+     * @return env
+     */
+    public ExecutionEnvironment getExecutionEnvironment() {
+        return env;
+    }
+
+    /**
+     * @return classLoaderFactory
+     */
+    public ClassLoaderFactory getClassLoaderFactory() {
+        return this.classLoaderFactory;
+    }
+
+    public String getJndiName() {
+        // TODO
+        return null;
+    }
+
+    protected synchronized void startService() {
+        if (this.started.get() == false) {
+            Problems problems = new SimpleProblems();
+
+            // ------------------------------------------------------------------------------------
+            // Read the configuration ...
+            // ------------------------------------------------------------------------------------
+            ValueFactories valueFactories = env.getValueFactories();
+            PathFactory pathFactory = valueFactories.getPathFactory();
+            NameFactory nameFactory = valueFactories.getNameFactory();
+
+            final String configurationSourceName = configurationProjection.getSourceName();
+            RepositoryConnectionFactory factory = sources.getConnectionFactory(configurationSourceName);
+            if (factory == null) {
+                throw new FederationException(RepositoryI18n.unableToFindRepositorySourceWithName.text(configurationSourceName));
+            }
+
+            // Create a federating command executor to execute the commands and merge the results into a single set of
+            // commands.
+            List<Projection> projections = Collections.singletonList(configurationProjection);
+            CommandExecutor executor = null;
+            if (configurationProjection.getRules().size() == 1) {
+                // There is just a single projection for the configuration repository, so just use an executor that
+                // translates the paths using the projection
+                executor = new SingleProjectionCommandExecutor(env, configurationSourceName, configurationProjection, sources);
+            } else if (configurationProjection.getRules().size() == 0) {
+                // There is no projection for the configuration repository, so just use a no-op executor
+                executor = new NoOpCommandExecutor(env, configurationSourceName);
+            } else {
+                // The configuration repository has more than one projection, so we need to merge the results
+                executor = new FederatingCommandExecutor(env, configurationSourceName, null, projections, sources);
+            }
+
+            // Read the configuration and the repository sources, located as child nodes/branches under "/dna:sources",
+            // and then instantiate and register each in the "sources" manager
+            Path configurationRoot = pathFactory.create("/");
+            try {
+                Path sourcesNode = pathFactory.create(configurationRoot, nameFactory.create("dna:sources"));
+                BasicGetChildrenCommand getSources = new BasicGetChildrenCommand(sourcesNode);
+                executor.execute(getSources);
+                if (getSources.hasNoError()) {
+
+                    // Build the commands to get each of the children ...
+                    List<Path.Segment> children = getSources.getChildren();
+                    if (!children.isEmpty()) {
+                        BasicCompositeCommand commands = new BasicCompositeCommand();
+                        for (Path.Segment child : getSources.getChildren()) {
+                            final Path pathToSource = pathFactory.create(sourcesNode, child);
+                            commands.add(new BasicGetNodeCommand(pathToSource));
+                        }
+                        executor.execute(commands);
+
+                        // Iterate over each source node obtained ...
+                        for (GraphCommand command : commands) {
+                            BasicGetNodeCommand getSourceCommand = (BasicGetNodeCommand)command;
+                            if (getSourceCommand.hasNoError()) {
+                                RepositorySource source = createRepositorySource(getSourceCommand.getPath(),
+                                                                                 getSourceCommand.getProperties(),
+                                                                                 problems);
+                                if (source != null) sources.addSource(source, true);
+                            }
+                        }
+                    }
+                }
+            } catch (Throwable err) {
+                throw new FederationException(RepositoryI18n.errorStartingRepositoryService.text());
+            }
+            this.started.set(true);
+        }
+    }
+
+    /**
+     * Instantiate the {@link RepositorySource} described by the supplied properties.
+     * 
+     * @param path the path to the node where these properties were found; never null
+     * @param properties the properties; never null
+     * @param problems the problems container in which any problems should be reported; never null
+     * @return the repository source instance, or null if it could not be created
+     */
+    @SuppressWarnings( "null" )
+    protected RepositorySource createRepositorySource( Path path,
+                                                       Map<Name, Property> properties,
+                                                       Problems problems ) {
+        ValueFactories valueFactories = env.getValueFactories();
+        NameFactory nameFactory = valueFactories.getNameFactory();
+        ValueFactory<String> stringFactory = valueFactories.getStringFactory();
+
+        // Get the classname and classpath ...
+        Property classnameProperty = properties.get(nameFactory.create(CLASSNAME_PROPERTY_NAME));
+        Property classpathProperty = properties.get(nameFactory.create(CLASSPATH_PROPERTY_NAME));
+        if (classnameProperty == null) {
+            problems.addError(RepositoryI18n.requiredPropertyIsMissingFromNode, CLASSNAME_PROPERTY_NAME, path);
+        }
+        // If the classpath property is null or empty, the default classpath will be used
+        if (problems.hasErrors()) return null;
+
+        // Create the instance ...
+        String classname = stringFactory.create(classnameProperty.getValues().next());
+        String[] classpath = classpathProperty == null ? new String[] {} : stringFactory.create(classpathProperty.getValuesAsArray());
+        ClassLoader classLoader = this.classLoaderFactory.getClassLoader(classpath);
+        RepositorySource source = null;
+        try {
+            Class<?> sourceClass = classLoader.loadClass(classname);
+            source = (RepositorySource)sourceClass.newInstance();
+        } catch (ClassNotFoundException err) {
+            problems.addError(err, RepositoryI18n.unableToLoadClassUsingClasspath, classname, classpath);
+        } catch (IllegalAccessException err) {
+            problems.addError(err, RepositoryI18n.unableToAccessClassUsingClasspath, classname, classpath);
+        } catch (Throwable err) {
+            problems.addError(err, RepositoryI18n.unableToInstantiateClassUsingClasspath, classname, classpath);
+        }
+
+        // Try to set the name property to the local name of the node...
+        Reflection reflection = new Reflection(source.getClass());
+        try {
+            reflection.invokeSetterMethodOnTarget("name", source, path.getLastSegment().getName().getLocalName());
+        } catch (SecurityException err) {
+            // Do nothing ... assume not a JavaBean property
+        } catch (NoSuchMethodException err) {
+            // Do nothing ... assume not a JavaBean property
+        } catch (IllegalArgumentException err) {
+            // Do nothing ... assume not a JavaBean property
+        } catch (IllegalAccessException err) {
+            // Do nothing ... assume not a JavaBean property
+        } catch (InvocationTargetException err) {
+            // Do nothing ... assume not a JavaBean property
+        }
+
+        // Now set all the properties that we can, ignoring any property that doesn't fit pattern ...
+        for (Map.Entry<Name, Property> entry : properties.entrySet()) {
+            Name propertyName = entry.getKey();
+            Property property = entry.getValue();
+            String javaPropertyName = propertyName.getLocalName();
+            if (property.isEmpty()) continue;
+            Object value = null;
+            if (property.isSingle()) {
+                value = property.getValues().next();
+            } else if (property.isMultiple()) {
+                value = property.getValuesAsArray();
+            }
+            try {
+                reflection.invokeSetterMethodOnTarget(javaPropertyName, source, value);
+            } catch (SecurityException err) {
+                // Do nothing ... assume not a JavaBean property
+            } catch (NoSuchMethodException err) {
+                // Do nothing ... assume not a JavaBean property
+            } catch (IllegalArgumentException err) {
+                // Do nothing ... assume not a JavaBean property
+            } catch (IllegalAccessException err) {
+                // Do nothing ... assume not a JavaBean property
+            } catch (InvocationTargetException err) {
+                // Do nothing ... assume not a JavaBean property
+            }
+        }
+        return source;
+    }
+
+    /**
+     * Get the current set of federated repository names.
+     * 
+     * @return the names of the repository, which is a mutable copy of the names that is not backed by the actual sources
+     */
+    public Set<String> getFederatedRepositoryNames() {
+        Set<String> repositoryNames = new HashSet<String>();
+        for (RepositorySource source : sources.getSources()) {
+            if (source instanceof FederatedRepositorySource) {
+                repositoryNames.add(source.getName());
+            }
+        }
+        return repositoryNames;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        return false;
+    }
+}


Property changes on: trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryService.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Copied: trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositorySourceManager.java (from rev 339, trunk/dna-repository/src/main/java/org/jboss/dna/repository/federation/RepositorySourceManager.java)
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositorySourceManager.java	                        (rev 0)
+++ trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositorySourceManager.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,348 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.repository;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import org.jboss.dna.repository.services.AbstractServiceAdministrator;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory;
+import org.jboss.dna.spi.graph.connection.RepositoryConnectionPool;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+
+/**
+ * @author Randall Hauch
+ */
+public class RepositorySourceManager implements RepositoryConnectionFactories {
+
+    /**
+     * The administrative component for this service.
+     * 
+     * @author Randall Hauch
+     */
+    protected class Administrator extends AbstractServiceAdministrator {
+
+        protected Administrator() {
+            super(RepositoryI18n.federationServiceName, State.STARTED);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected void doStart( State fromState ) {
+            super.doStart(fromState);
+            RepositorySourceManager.this.start();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected void doShutdown( State fromState ) {
+            super.doShutdown(fromState);
+            RepositorySourceManager.this.shutdown();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean awaitTermination( long timeout,
+                                         TimeUnit unit ) throws InterruptedException {
+            return RepositorySourceManager.this.awaitTermination(timeout, unit);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        protected boolean doCheckIsTerminated() {
+            return RepositorySourceManager.this.isTerminated();
+        }
+
+    }
+
+    private final ServiceAdministrator administrator = new Administrator();
+    private final ReadWriteLock sourcesLock = new ReentrantReadWriteLock();
+    private final CopyOnWriteArrayList<RepositorySource> sources = new CopyOnWriteArrayList<RepositorySource>();
+    private RepositoryConnectionFactories delegate;
+
+    /**
+     * Create a new manager instance.
+     * 
+     * @param delegate the factories object that this instance should delegate to in the event that a source is not found in this
+     *        manager; may be null if there is no delegate
+     */
+    public RepositorySourceManager( RepositoryConnectionFactories delegate ) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * @return delegate
+     */
+    public RepositoryConnectionFactories getDelegate() {
+        return delegate;
+    }
+
+    /**
+     * @param delegate Sets delegate to the specified value.
+     */
+    public void setDelegate( RepositoryConnectionFactories delegate ) {
+        this.delegate = delegate;
+    }
+
+    /**
+     * @return administrator
+     */
+    public ServiceAdministrator getAdministrator() {
+        return this.administrator;
+    }
+
+    /**
+     * Utility method called by the administrator.
+     */
+    protected void start() {
+        // Do not establish connections to the sources; these will be established as needed
+
+    }
+
+    /**
+     * Utility method called by the administrator.
+     */
+    protected void shutdown() {
+        // Close all connections to the sources. This is done inside the sources write lock.
+        try {
+            this.sourcesLock.readLock().lock();
+            for (RepositorySource source : this.sources) {
+                if (source instanceof RepositoryConnectionPool) {
+                    RepositoryConnectionPool pool = (RepositoryConnectionPool)source;
+                    pool.shutdown();
+                }
+            }
+        } finally {
+            this.sourcesLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Utility method called by the administrator.
+     * 
+     * @param timeout
+     * @param unit
+     * @return true if all pools were terminated in the supplied time (or were already terminated), or false if the timeout
+     *         occurred before all the connections were closed
+     * @throws InterruptedException
+     */
+    protected boolean awaitTermination( long timeout,
+                                        TimeUnit unit ) throws InterruptedException {
+        // Check whether all source pools are shut down. This is done inside the sources write lock.
+        try {
+            this.sourcesLock.readLock().lock();
+            for (RepositorySource source : this.sources) {
+                if (source instanceof RepositoryConnectionPool) {
+                    RepositoryConnectionPool pool = (RepositoryConnectionPool)source;
+                    if (!pool.awaitTermination(timeout, unit)) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        } finally {
+            this.sourcesLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Returns true if this federated repository is in the process of terminating after {@link ServiceAdministrator#shutdown()}
+     * has been called on the {@link #getAdministrator() administrator}, but the federated repository has connections that have
+     * not yet normally been {@link RepositoryConnection#close() closed}. This method may be useful for debugging. A return of
+     * <tt>true</tt> reported a sufficient period after shutdown may indicate that connection users have ignored or suppressed
+     * interruption, causing this repository not to properly terminate.
+     * 
+     * @return true if terminating but not yet terminated, or false otherwise
+     * @see #isTerminated()
+     */
+    public boolean isTerminating() {
+        try {
+            this.sourcesLock.readLock().lock();
+            for (RepositorySource source : this.sources) {
+                if (source instanceof RepositoryConnectionPool) {
+                    RepositoryConnectionPool pool = (RepositoryConnectionPool)source;
+                    if (pool.isTerminating()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        } finally {
+            this.sourcesLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Return true if this federated repository has completed its termination and no longer has any open connections.
+     * 
+     * @return true if terminated, or false otherwise
+     * @see #isTerminating()
+     */
+    public boolean isTerminated() {
+        try {
+            this.sourcesLock.readLock().lock();
+            for (RepositorySource source : this.sources) {
+                if (source instanceof RepositoryConnectionPool) {
+                    RepositoryConnectionPool pool = (RepositoryConnectionPool)source;
+                    if (!pool.isTerminated()) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        } finally {
+            this.sourcesLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Get an unmodifiable collection of {@link RepositorySource federated sources}.
+     * <p>
+     * This method can safely be called while the federation repository is in use.
+     * </p>
+     * 
+     * @return the sources
+     */
+    public Collection<RepositorySource> getSources() {
+        return Collections.unmodifiableCollection(this.sources);
+    }
+
+    /**
+     * Add the supplied federated source. This method returns false if the source is null.
+     * <p>
+     * This method can safely be called while the federation repository is in use.
+     * </p>
+     * 
+     * @param source the source to add
+     * @param force true if the valid source should be added even if there is an existing source with the supplied name
+     * @return true if the source is added, or false if the reference is null or if there is already an existing source with the
+     *         supplied name.
+     */
+    public boolean addSource( RepositorySource source,
+                              boolean force ) {
+        if (source == null) return false;
+        try {
+            this.sourcesLock.writeLock().lock();
+            for (RepositorySource existingSource : this.sources) {
+                if (existingSource.getName().equals(source.getName())) return false;
+            }
+            return force ? this.sources.add(source) : this.sources.addIfAbsent(source);
+        } finally {
+            this.sourcesLock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Remove from this federated repository the supplied source (or a source with the same name as that supplied). This call
+     * shuts down the connections in the source in an orderly fashion, allowing those connection currently in use to be used and
+     * closed normally, but preventing further connections from being used.
+     * <p>
+     * This method can safely be called while the federation repository is in use.
+     * </p>
+     * 
+     * @param source the source to be removed
+     * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
+     *        should not wait at all
+     * @param unit the time unit to be used for <code>timeToAwait</code>
+     * @return true if the source was removed, or false if the source was not a source for this repository.
+     * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
+     */
+    public boolean removeSource( RepositorySource source,
+                                 long timeToAwait,
+                                 TimeUnit unit ) throws InterruptedException {
+        // Use the name; don't use the object equality ...
+        return removeSource(source.getName(), timeToAwait, unit) != null;
+    }
+
+    /**
+     * Remove from this federated repository the source with the supplied name. This call shuts down the connections in the source
+     * in an orderly fashion, allowing those connection currently in use to be used and closed normally, but preventing further
+     * connections from being used.
+     * <p>
+     * This method can safely be called while the federation repository is in use.
+     * </p>
+     * 
+     * @param name the name of the source to be removed
+     * @param timeToAwait the amount of time to wait while all of the source's connections are closed, or non-positive if the call
+     *        should not wait at all
+     * @param unit the time unit to be used for <code>timeToAwait</code>
+     * @return the source with the supplied name that was removed, or null if no existing source matching the supplied name could
+     *         be found
+     * @throws InterruptedException if the thread is interrupted while awaiting closing of the connections
+     */
+    public RepositorySource removeSource( String name,
+                                          long timeToAwait,
+                                          TimeUnit unit ) throws InterruptedException {
+        try {
+            this.sourcesLock.writeLock().lock();
+            for (RepositorySource existingSource : this.sources) {
+                if (existingSource.getName().equals(name)) {
+                    // Shut down the connection pool if it is one ...
+                    if (existingSource instanceof RepositoryConnectionPool) {
+                        RepositoryConnectionPool pool = (RepositoryConnectionPool)existingSource;
+                        pool.shutdown();
+                        if (timeToAwait > 0l) pool.awaitTermination(timeToAwait, unit);
+                    }
+                }
+                return existingSource;
+            }
+        } finally {
+            this.sourcesLock.writeLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.connection.RepositoryConnectionFactories#getConnectionFactory(java.lang.String)
+     */
+    public RepositoryConnectionFactory getConnectionFactory( String sourceName ) {
+        try {
+            this.sourcesLock.readLock().lock();
+            for (RepositorySource existingSource : this.sources) {
+                if (existingSource.getName().equals(sourceName)) return existingSource;
+            }
+            RepositoryConnectionFactories delegate = this.delegate;
+            if (delegate != null) {
+                return delegate.getConnectionFactory(sourceName);
+            }
+        } finally {
+            this.sourcesLock.readLock().unlock();
+        }
+        return null;
+    }
+
+}

Modified: trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties
===================================================================
--- trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties	2008-07-24 19:52:13 UTC (rev 369)
@@ -23,11 +23,12 @@
 serviceShutdowAndMayNotBeStarted = The {0} has been shutdown and may not be (re)started
 serviceShutdowAndMayNotBePaused = The {0} has been shutdown and my not be paused
 serviceNotShutdowAndMayNotBeTerminated = The {0} has not been shutdown and may not be terminated
-unableToFindRepositoryInJndi = Unable to find a JCR repository in JNDI at "{0}"
-unableToRegisterRepositoryInJndi = Unable to register a JCR repository in JNDI at "{0}"
-unableToUnregisterRepositoryInJndi = Unable to unregister a JCR repository at JNDI at "{0}"
-unableToRemoveRepository = Unable to remove a JCR repository named "{0}"
-unableToFindRepositoryWithName = Unable to find a JCR repository named "{0}"
+unableToFindRepositoryInJndi = Unable to find a repository in JNDI at "{0}"
+unableToRegisterRepositoryInJndi = Unable to register a repository in JNDI at "{0}"
+unableToUnregisterRepositoryInJndi = Unable to unregister a repository at JNDI at "{0}"
+unableToRemoveRepository = Unable to remove a repository named "{0}"
+unableToFindRepositoryWithName = Unable to find a repository named "{0}"
+unableToFindRepositorySourceWithName = Unable to find a repository source named "{0}"
 errorProcessingEvents = Error processing events from {0}
 errorFindingPropertyNameInPropertyAddedEvent = Error finding the name of the added property in the event path {0}
 errorFindingPropertyNameInPropertyChangedEvent = Error finding the name of the changed property in the event path {0}
@@ -101,14 +102,4 @@
 canceledSequencingXmlDocument = Canceled sequencing XML
 warningSequencingXmlDocument = A warning was received while sequencing XML: {0}
 
-interruptedWhileConnectingToFederationConfigurationRepository = Interrupted while connecting to federation configuration repository "{0}"
-interruptedWhileUsingFederationConfigurationRepository = Interrupted while using federation configuration repository "{0}"
-interruptedWhileClosingConnectionToFederationConfigurationRepository = Interrupted while closing connection to federation configuration repository "{0}"
-federatedRepositoryCannotBeFound = The federated repository "{0}" cannot be found
-unableToConnectToFederationConfigurationRepository = Unable to connect to federation configuration repository "{0}"
-unableToFindRepositorySourceByName = Unable to find the repository source "{0}"
-unableToCreateConnectionToFederatedRepository = Unable to connect to the repository "{0}". Check the Federation Service configuration.
-unableToAuthenticateConnectionToFederatedRepository = Unable to authenticate "{1}" for repository "{0}"
-repositoryHasBeenShutDown = The "{0}" repository has been shut down and may no longer be used.
-repositoryPathInFederationBindingIsNotAbsolute = The repository path in a federation binding must be absolute, but was "{0}"
-errorReadingMergePlan = Error while reading merge plan for {0}
\ No newline at end of file
+errorStartingRepositoryService = Error while starting repository service

Copied: trunk/dna-repository/src/test/java/org/jboss/dna/repository/RepositoryServiceTest.java (from rev 346, trunk/dna-repository/src/test/java/org/jboss/dna/repository/federation/FederationServiceTest.java)
===================================================================
--- trunk/dna-repository/src/test/java/org/jboss/dna/repository/RepositoryServiceTest.java	                        (rev 0)
+++ trunk/dna-repository/src/test/java/org/jboss/dna/repository/RepositoryServiceTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,293 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.repository;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItems;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.stub;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import java.util.concurrent.TimeUnit;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.connector.federation.FederationException;
+import org.jboss.dna.connector.federation.Projection;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.spi.graph.NamespaceRegistry;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.PropertyFactory;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.SimpleRepository;
+import org.jboss.dna.spi.graph.connection.SimpleRepositorySource;
+import org.jboss.dna.spi.graph.impl.BasicNamespaceRegistry;
+import org.jboss.dna.spi.graph.impl.BasicPropertyFactory;
+import org.jboss.dna.spi.graph.impl.StandardValueFactories;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+
+/**
+ * @author Randall Hauch
+ */
+public class RepositoryServiceTest {
+
+    public static final String CLASSNAME = RepositoryService.CLASSNAME_PROPERTY_NAME;
+    public static final String CLASSPATH = RepositoryService.CLASSPATH_PROPERTY_NAME;
+    public static final String PROJECTION_RULES = RepositoryService.PROJECTION_RULES_PROPERTY_NAME;
+    public static final String TIME_TO_EXPIRE = RepositoryService.CACHE_POLICY_TIME_TO_EXPIRE;
+    public static final String TIME_TO_CACHE = RepositoryService.CACHE_POLICY_TIME_TO_CACHE;
+
+    private RepositoryService service;
+    private Projection configProjection;
+    private StandardValueFactories valueFactories;
+    private PropertyFactory propertyFactory;
+    private PathFactory pathFactory;
+    private String configSourceName;
+    private SimpleRepository configRepository;
+    private SimpleRepositorySource configRepositorySource;
+    @Mock
+    private ExecutionEnvironment env;
+    @Mock
+    private RepositorySourceManager sources;
+
+    @Before
+    public void beforeEach() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        NamespaceRegistry registry = new BasicNamespaceRegistry();
+        registry.register("dna", "http://www.jboss.org/dna");
+        valueFactories = new StandardValueFactories(registry);
+        pathFactory = valueFactories.getPathFactory();
+        propertyFactory = new BasicPropertyFactory(valueFactories);
+        Path pathInRepository = pathFactory.create("/");
+        Path pathInSource = pathFactory.create("/reposX");
+        configSourceName = "configSource";
+        Projection.Rule configProjectionRule = new Projection.PathRule(pathInRepository, pathInSource);
+        configProjection = new Projection(configSourceName, configProjectionRule);
+        configRepository = new SimpleRepository("Configuration Repository");
+        configRepositorySource = new SimpleRepositorySource();
+        configRepositorySource.setRepositoryName(configRepository.getRepositoryName());
+        configRepositorySource.setName(configSourceName);
+        stub(sources.getConnectionFactory(configSourceName)).toReturn(configRepositorySource);
+        stub(env.getValueFactories()).toReturn(valueFactories);
+        stub(env.getPropertyFactory()).toReturn(propertyFactory);
+        stub(env.getNamespaceRegistry()).toReturn(registry);
+        service = new RepositoryService(sources, configProjection, env, null);
+    }
+
+    @After
+    public void afterEach() throws Exception {
+        service.getAdministrator().shutdown();
+        service.getAdministrator().awaitTermination(4, TimeUnit.SECONDS);
+        SimpleRepository.shutdownAll();
+        Logger.getLogger(getClass()).trace("");
+    }
+
+    @Test
+    public void shouldHaveServiceAdministratorAfterInstantiation() {
+        assertThat(service.getAdministrator(), is(notNullValue()));
+    }
+
+    @Test
+    public void shouldHaveConfigurationRegionAfterInstantiation() {
+        assertThat(service.getConfigurationProjection(), is(notNullValue()));
+        assertThat(service.getConfigurationProjection(), is(sameInstance(configProjection)));
+    }
+
+    @Test
+    public void shouldHaveAnExecutionEnvironmentAfterInstantiation() {
+        assertThat(service.getExecutionEnvironment(), is(notNullValue()));
+        assertThat(service.getExecutionEnvironment(), is(sameInstance(env)));
+    }
+
+    @Test
+    public void shouldHaveNonNullClassLoaderFactoryAfterInstantiatingWithNullClassLoaderFactoryReference() {
+        assertThat(service.getClassLoaderFactory(), is(notNullValue()));
+    }
+
+    @Test
+    public void shouldHaveNonNullClassLoaderFactoryAfterInstantiatingWithClassLoaderFactoryReference() {
+        ClassLoaderFactory classLoaderFactory = mock(ClassLoaderFactory.class);
+        service = new RepositoryService(sources, configProjection, env, classLoaderFactory);
+        assertThat(service.getClassLoaderFactory(), is(notNullValue()));
+        assertThat(service.getClassLoaderFactory(), is(sameInstance(classLoaderFactory)));
+    }
+
+    @Test
+    public void shouldHaveNullJndiNameAfterInstantiation() {
+        assertThat(service.getJndiName(), is(nullValue()));
+    }
+
+    @Test
+    public void shouldAllowShuttingDownBeforeStartingUp() throws Exception {
+        assertThat(service.getAdministrator().getState(), is(ServiceAdministrator.State.PAUSED));
+        service.getAdministrator().shutdown();
+        service.getAdministrator().awaitTermination(1, TimeUnit.SECONDS);
+        assertThat(service.getAdministrator().getState(), is(ServiceAdministrator.State.TERMINATED));
+    }
+
+    @Test( expected = FederationException.class )
+    public void shouldFailToStartUpIfConfigurationRepositorySourceIsNotFound() {
+        stub(sources.getConnectionFactory(configSourceName)).toReturn(null);
+        service.getAdministrator().start();
+    }
+
+    @Test( expected = FederationException.class )
+    public void shouldFailToStartUpIfUnableToConnectToConfigurationRepository() throws Exception {
+        RepositorySource mockSource = mock(RepositorySource.class);
+        stub(sources.getConnectionFactory(configSourceName)).toReturn(mockSource);
+        stub(mockSource.getConnection()).toThrow(new UnsupportedOperationException());
+        service.getAdministrator().start();
+    }
+
+    @Test( expected = FederationException.class )
+    public void shouldFailToStartUpIfInterruptedWhileConnectingToConfigurationRepository() throws Exception {
+        RepositorySource mockSource = mock(RepositorySource.class);
+        stub(sources.getConnectionFactory(configSourceName)).toReturn(mockSource);
+        stub(mockSource.getConnection()).toThrow(new InterruptedException());
+        service.getAdministrator().start();
+    }
+
+    @Test
+    public void shouldStartUpUsingConfigurationRepositoryThatContainsSomeSources() {
+        // Use a real source manager for this test ...
+        sources = new RepositorySourceManager(sources);
+        sources.addSource(configRepositorySource, true);
+        assertThat(sources.getSources(), hasItems((RepositorySource)configRepositorySource));
+        assertThat(sources.getSources().size(), is(1));
+        service = new RepositoryService(sources, configProjection, env, null);
+
+        // Set up the configuration repository to contain 3 sources ...
+        configRepository.create(env, "/reposX/dna:sources");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", CLASSNAME, SimpleRepositorySource.class.getName());
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", CLASSPATH, "");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", "repositoryName", "sourceReposA");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", "retryLimit", 3);
+
+        configRepository.setProperty(env, "/reposX/dna:sources/source B", CLASSNAME, SimpleRepositorySource.class.getName());
+        configRepository.setProperty(env, "/reposX/dna:sources/source B", CLASSPATH, "");
+        configRepository.setProperty(env, "/reposX/dna:sources/source B", "repositoryName", "sourceReposB");
+
+        configRepository.setProperty(env, "/reposX/dna:sources/source C", CLASSNAME, SimpleRepositorySource.class.getName());
+        configRepository.setProperty(env, "/reposX/dna:sources/source C", CLASSPATH, "");
+        configRepository.setProperty(env, "/reposX/dna:sources/source C", "repositoryName", "sourceReposC");
+
+        // Now, start up the service ...
+        service.getAdministrator().start();
+
+        // and verify that the sources were added to the manager...
+        assertThat(sources.getSources().size(), is(4));
+        assertThat(sources.getConnectionFactory("source A"), is(instanceOf(SimpleRepositorySource.class)));
+        assertThat(sources.getConnectionFactory("source B"), is(instanceOf(SimpleRepositorySource.class)));
+        assertThat(sources.getConnectionFactory("source C"), is(instanceOf(SimpleRepositorySource.class)));
+
+        SimpleRepositorySource sourceA = (SimpleRepositorySource)sources.getConnectionFactory("source A");
+        assertThat(sourceA.getName(), is("source A"));
+        assertThat(sourceA.getRepositoryName(), is("sourceReposA"));
+        assertThat(sourceA.getRetryLimit(), is(3));
+
+        SimpleRepositorySource sourceB = (SimpleRepositorySource)sources.getConnectionFactory("source B");
+        assertThat(sourceB.getName(), is("source B"));
+        assertThat(sourceB.getRepositoryName(), is("sourceReposB"));
+        assertThat(sourceB.getRetryLimit(), is(SimpleRepositorySource.DEFAULT_RETRY_LIMIT));
+
+        SimpleRepositorySource sourceC = (SimpleRepositorySource)sources.getConnectionFactory("source C");
+        assertThat(sourceC.getName(), is("source C"));
+        assertThat(sourceC.getRepositoryName(), is("sourceReposC"));
+        assertThat(sourceC.getRetryLimit(), is(SimpleRepositorySource.DEFAULT_RETRY_LIMIT));
+    }
+
+    @Test
+    public void shouldStartUpUsingConfigurationRepositoryThatContainsNoSources() {
+        // Set up the configuration repository to contain NO sources ...
+        configRepository.create(env, "/reposX/dna:sources");
+
+        // Now, start up the service ...
+        service.getAdministrator().start();
+
+        // and verify that the configuration source was obtained from the manager ...
+        verify(sources, times(2)).getConnectionFactory(configSourceName); // once for checking source, second for getting
+
+        // and verify that the sources were never added to the manager...
+        verifyNoMoreInteractions(sources);
+    }
+
+    @Test
+    public void shouldStartUpAndCreateRepositoryUsingConfigurationRepositoryThatContainsNoSources() {
+        // Set up the configuration repository ...
+        configRepository.create(env, "/reposX/dna:sources");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", CLASSNAME, SimpleRepositorySource.class.getName());
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", CLASSPATH, "");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", "repositoryName", "sourceReposA");
+        configRepository.setProperty(env, "/reposX/dna:sources/source A", "retryLimit", 3);
+
+        String fedReposPath = "/reposX/dna:repositories/fed repos/";
+        configRepository.setProperty(env, fedReposPath, TIME_TO_CACHE, "10000");
+        configRepository.setProperty(env, fedReposPath, TIME_TO_EXPIRE, "20000");
+        configRepository.setProperty(env, fedReposPath + "dna:regions/source A", PROJECTION_RULES, "/a/b/c => /sx/sy");
+        configRepository.setProperty(env, fedReposPath + "dna:regions/source B", PROJECTION_RULES, "/ => /");
+        configRepository.setProperty(env, fedReposPath + "dna:regions/source C", PROJECTION_RULES, "/d/e/f => /");
+        configRepository.setProperty(env, fedReposPath + "dna:regions/source D", PROJECTION_RULES, "/ => /x/y/z");
+
+        // Now, start up the service ...
+        service.getAdministrator().start();
+
+        // // Create the repository ...
+        // FederatedRepositorySource repository = (FederatedRepositorySource)sources.getConnectionFactory("fed repos");
+        // assertThat(repository, is(notNullValue()));
+        // assertThat(repository.getName(), is("fed repos"));
+        // assertThat(repository.getDefaultCachePolicy().getTimeToCache(), is(10000l));
+        // assertThat(repository.getDefaultCachePolicy().getTimeToExpire(), is(20000l));
+        // assertThat(repository.getCacheProjection(), is(sameInstance(configProjection)));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(0).getPathsInSource(pathFactory.create("/a/b/c"),
+        // pathFactory),
+        // hasItems(pathFactory.create("/sx/sy")));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(0).getSourceName(), is("source A"));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(1).getPathsInSource(pathFactory.create("/"),
+        // pathFactory),
+        // hasItems(pathFactory.create("/")));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(1).getSourceName(), is("source B"));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(2).getPathsInSource(pathFactory.create("/d/e/f"),
+        // pathFactory),
+        // hasItems(pathFactory.create("/")));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(2).getSourceName(), is("source C"));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(3).getPathsInSource(pathFactory.create("/"),
+        // pathFactory),
+        // hasItems(pathFactory.create("/x/y/z")));
+        // assertThat(repository.getConfiguration().getSourceProjections().get(3).getSourceName(), is("source A"));
+        //
+        // for (int i = 0; i != 10; ++i) {
+        // assertThat(service.getRepository("fed repos"), is(sameInstance(repository)));
+        // }
+    }
+
+}

Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/ExecutionContextFactory.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/ExecutionContextFactory.java	                        (rev 0)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/ExecutionContextFactory.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,91 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+
+/**
+ * A factory for creating {@link ExecutionEnvironment} instances. Each execution environment is affiliated with a JAAS
+ * {@link Subject}, and thus the factory methods take the same parameters that the JAAS {@link LoginContext} take.
+ * 
+ * @author Randall Hauch
+ */
+public interface ExecutionContextFactory {
+
+    /**
+     * Create an {@link ExecutionEnvironment} for the supplied {@link LoginContext}.
+     * 
+     * @param loginContext the JAAS login context
+     * @return the execution context
+     * @throws IllegalArgumentException if the <code>loginContext</code> is null
+     */
+    ExecutionEnvironment create( LoginContext loginContext );
+
+    /**
+     * @param name the name of the JAAS login context
+     * @return the execution context
+     * @throws IllegalArgumentException if the <code>name</code> is null
+     * @throws LoginException if there <code>name</code> is invalid (or there is no login context named "other"), or if the
+     *         default callback handler JAAS property was not set or could not be loaded
+     */
+    ExecutionEnvironment create( String name ) throws LoginException;
+
+    /**
+     * @param name the name of the JAAS login context
+     * @param subject the subject to authenticate
+     * @return the execution context
+     * @throws LoginException if there <code>name</code> is invalid (or there is no login context named "other"), if the default
+     *         callback handler JAAS property was not set or could not be loaded, or if the <code>subject</code> is null or
+     *         unknown
+     */
+    ExecutionEnvironment create( String name,
+                                 Subject subject ) throws LoginException;
+
+    /**
+     * @param name the name of the JAAS login context
+     * @param callbackHandler the callback handler that will be used by {@link LoginModule}s to communicate with the user.
+     * @return the execution context
+     * @throws LoginException if there <code>name</code> is invalid (or there is no login context named "other"), or if the
+     *         <code>callbackHandler</code> is null
+     */
+    ExecutionEnvironment create( String name,
+                                 CallbackHandler callbackHandler ) throws LoginException;
+
+    /**
+     * @param name the name of the JAAS login context
+     * @param subject the subject to authenticate
+     * @param callbackHandler the callback handler that will be used by {@link LoginModule}s to communicate with the user.
+     * @return the execution context
+     * @throws LoginException if there <code>name</code> is invalid (or there is no login context named "other"), if the default
+     *         callback handler JAAS property was not set or could not be loaded, if the <code>subject</code> is null or unknown,
+     *         or if the <code>callbackHandler</code> is null
+     */
+    ExecutionEnvironment create( String name,
+                                 Subject subject,
+                                 CallbackHandler callbackHandler ) throws LoginException;
+
+}

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/SpiI18n.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -59,6 +59,11 @@
     public static I18n pathExpressionHasInvalidSelect;
     public static I18n pathExpressionHasInvalidMatch;
 
+    public static I18n executingGraphCommand;
+    public static I18n executedGraphCommand;
+    public static I18n closingCommandExecutor;
+    public static I18n closedCommandExecutor;
+
     static {
         try {
             I18n.initialize(SpiI18n.class);

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Path.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Path.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/Path.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -105,14 +105,14 @@
     public static final TextDecoder URL_DECODER = new UrlEncoder().setSlashEncoded(true);
 
     /**
-     * The default text encoder to be used when none is otherwise specified. This is currently the
-     * {@link #JSR283_ENCODER JSR-283 encoder}.
+     * The default text encoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283
+     * encoder}.
      */
     public static final TextEncoder DEFAULT_ENCODER = JSR283_ENCODER;
 
     /**
-     * The default text decoder to be used when none is otherwise specified. This is currently the
-     * {@link #JSR283_ENCODER JSR-283 encoder}.
+     * The default text decoder to be used when none is otherwise specified. This is currently the {@link #JSR283_ENCODER JSR-283
+     * encoder}.
      */
     public static final TextDecoder DEFAULT_DECODER = JSR283_DECODER;
 
@@ -274,20 +274,40 @@
     public boolean isRoot();
 
     /**
-     * Determine whether this path represents the same as the supplied path. This is equivalent to calling
-     * <code>this.compareTo(other) == 0 </code>.
+     * Determine whether this path represents the same as the supplied path. This is equivalent to calling <code>
+     * this.compareTo(other) == 0 </code>.
      * 
-     * @param other the other path to compare with this path
+     * @param other the other path to compare with this path; may be null
      * @return true if the paths are equivalent, or false otherwise
      */
-    public boolean isSame( Path other );
+    public boolean isSameAs( Path other );
 
     /**
+     * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isAncestorOf(Path) ancestor of} the
+     * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) ||
+     * isAncestor(other)</code>, and is a convenience method that is identical to calling <code>other.isAtOrBelow(this)</code>.
+     * 
+     * @param other the other path to compare with this path; may be null
+     * @return true if the paths are equivalent or if this path is considered an ancestor of the other path, or false otherwise
+     */
+    public boolean isAtOrAbove( Path other );
+
+    /**
+     * Determine whether this path is the {@link #isSameAs(Path) same as} to or a {@link #isDecendantOf(Path) decendant of} the
+     * supplied path. This method is equivalent to (but may be more efficient than) calling <code>isSame(other) ||
+     * isAncestor(other)</code>.
+     * 
+     * @param other the other path to compare with this path; may be null
+     * @return true if the paths are equivalent or if this path is considered a decendant of the other path, or false otherwise
+     */
+    public boolean isAtOrBelow( Path other );
+
+    /**
      * Determine whether this path is an ancestor of the supplied path. A path is considered an ancestor of another path if the
      * the ancestor path appears in its entirety at the beginning of the decendant path, and where the decendant path contains at
      * least one additional segment.
      * 
-     * @param decendant the path that may be the decendant
+     * @param decendant the path that may be the decendant; may be null
      * @return true if this path is an ancestor of the supplied path, or false otherwise
      */
     public boolean isAncestorOf( Path decendant );
@@ -296,14 +316,14 @@
      * Determine whether this path is an decendant of the supplied path. A path is considered a decendant of another path if the
      * the decendant path starts exactly with the entire ancestor path but contains at least one additional segment.
      * 
-     * @param ancestor the path that may be the ancestor
+     * @param ancestor the path that may be the ancestor; may be null
      * @return true if this path is an decendant of the supplied path, or false otherwise
      */
     public boolean isDecendantOf( Path ancestor );
 
     /**
-     * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute
-     * path starts with a "/".
+     * Return whether this path is an absolute path. A path is either relative or {@link #isAbsolute() absolute}. An absolute path
+     * starts with a "/".
      * 
      * @return true if the path is absolute, or false otherwise
      */
@@ -372,11 +392,11 @@
     public Path getAncestor();
 
     /**
-     * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is
-     * <code>x</code> levels up along the path. For example, <code>degree = 0</code> returns this path, while
-     * <code>degree = 1</code> returns the parent of this path, <code>degree = 2</code> returns the grandparent of this path,
-     * and so on. Note that the result may be unexpected if this path is not {@link #isNormalized() normalized}, as a
-     * non-normalized path contains ".." and "." segments.
+     * Return the path to the ancestor of the supplied degree. An ancestor of degree <code>x</code> is the path that is <code>x
+     * </code> levels up along the path. For example, <code>degree = 0</code> returns this path, while <code>degree = 1</code>
+     * returns the parent of this path, <code>degree = 2</code> returns the grandparent of this path, and so on. Note that the
+     * result may be unexpected if this path is not {@link #isNormalized() normalized}, as a non-normalized path contains ".." and
+     * "." segments.
      * 
      * @param degree
      * @return the ancestor of the supplied degree
@@ -426,21 +446,20 @@
      * 
      * @param beginIndex the beginning index, inclusive.
      * @return the specified subpath
-     * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this
-     *            <code>Path</code> object
+     * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative or larger than the length of this <code>
+     *            Path</code> object
      */
     public Path subpath( int beginIndex );
 
     /**
-     * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the
-     * <code>endIndex</code> index (exclusive).
+     * Return a new path consisting of the segments between the <code>beginIndex</code> index (inclusive) and the <code>endIndex
+     * </code> index (exclusive).
      * 
      * @param beginIndex the beginning index, inclusive.
      * @param endIndex the ending index, exclusive.
      * @return the specified subpath
-     * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger
-     *            than the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than
-     *            <code>endIndex</code>.
+     * @exception IndexOutOfBoundsException if the <code>beginIndex</code> is negative, or <code>endIndex</code> is larger than
+     *            the length of this <code>Path</code> object, or <code>beginIndex</code> is larger than <code>endIndex</code>.
      */
     public Path subpath( int beginIndex,
                          int endIndex );

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/PathFactory.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/PathFactory.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/PathFactory.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -60,8 +60,8 @@
     Path createAbsolutePath( Path.Segment... segments );
 
     /**
-     * Create an empty relative path (i.e., equivalent to {@link #createRelativePath(Path.Segment...) createRelativePath}({@link Path#SELF_SEGMENT})).
-     * Subsequent calls will always return the same instance.
+     * Create an empty relative path (i.e., equivalent to {@link #createRelativePath(Path.Segment...) createRelativePath}(
+     * {@link Path#SELF_SEGMENT})). Subsequent calls will always return the same instance.
      * 
      * @return the new path
      */
@@ -136,12 +136,23 @@
                  Path.Segment... segments );
 
     /**
+     * Create a path by appending the supplied names to the parent path.
+     * 
+     * @param parentPath the path that is to provide the basis for the new path
+     * @param subpath the subpath to be appended to the parent path, which must be in the form of a relative path
+     * @return the new path
+     * @throws IllegalArgumentException if the parent path reference or the segment name is null, or if the index is invalid
+     */
+    Path create( Path parentPath,
+                 String subpath );
+
+    /**
      * Create a path segment given the supplied segment name. The resulting segment will have no index.
      * 
      * @param segmentName the name of the segment
      * @return the segment
-     * @throws IllegalArgumentException if the segment name reference is <code>null</code> or the value could not be created
-     *         from the supplied string
+     * @throws IllegalArgumentException if the segment name reference is <code>null</code> or the value could not be created from
+     *         the supplied string
      */
     Path.Segment createSegment( String segmentName );
 
@@ -151,8 +162,8 @@
      * @param segmentName the name of the segment
      * @param decoder the decoder that should be used to decode the qualified name
      * @return the segment
-     * @throws IllegalArgumentException if the segment name reference is <code>null</code> or the value could not be created
-     *         from the supplied string
+     * @throws IllegalArgumentException if the segment name reference is <code>null</code> or the value could not be created from
+     *         the supplied string
      */
     Path.Segment createSegment( String segmentName,
                                 TextDecoder decoder );

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutor.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutor.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -21,6 +21,7 @@
  */
 package org.jboss.dna.spi.graph.commands.executor;
 
+import org.jboss.dna.spi.graph.DateTime;
 import org.jboss.dna.spi.graph.commands.CompositeCommand;
 import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
 import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
@@ -58,13 +59,21 @@
 
     private final ExecutionEnvironment env;
     private final String sourceName;
+    private final DateTime nowInUtc;
 
     protected AbstractCommandExecutor( ExecutionEnvironment env,
                                        String sourceName ) {
+        this(env, sourceName, null);
+    }
+
+    protected AbstractCommandExecutor( ExecutionEnvironment env,
+                                       String sourceName,
+                                       DateTime now ) {
         assert env != null;
         assert sourceName != null && sourceName.trim().length() != 0;
         this.env = env;
         this.sourceName = sourceName;
+        this.nowInUtc = now != null ? now.toUtcTimeZone() : env.getValueFactories().getDateFactory().createUtc();
     }
 
     /**
@@ -86,6 +95,15 @@
     }
 
     /**
+     * Get the current time associated with this executor. All calls to this method will result in the same time.
+     * 
+     * @return the current time expressed in UTC
+     */
+    public DateTime getCurrentTimeInUtc() {
+        return nowInUtc;
+    }
+
+    /**
      * {@inheritDoc}
      * <p>
      * This implementation examines the instance to see which {@link GraphCommand command interfaces} are implemented by the
@@ -101,6 +119,9 @@
             return;
         }
         // The command could implement multiple "get" behaviors
+        if (command instanceof GetNodeCommand) {
+            execute((GetNodeCommand)command);
+        }
         if (command instanceof GetPropertiesCommand) {
             execute((GetPropertiesCommand)command);
         }
@@ -145,7 +166,22 @@
 
     /**
      * {@inheritDoc}
+     * <p>
+     * This method implementation simply delegates to both the {@link #execute(GetPropertiesCommand)} and
+     * {@link #execute(GetChildrenCommand)} methods, and should be overridden by subclasses that can process
+     * {@link GetNodeCommand} more efficiently as a single command.
+     * </p>
      * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        execute((GetPropertiesCommand)command);
+        execute((GetChildrenCommand)command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
      * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
      */
     @SuppressWarnings( "unused" )

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/CommandExecutor.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/CommandExecutor.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/CommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -27,6 +27,7 @@
 import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
 import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
 import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
 import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
 import org.jboss.dna.spi.graph.commands.GraphCommand;
 import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
@@ -60,6 +61,17 @@
     void execute( CompositeCommand command ) throws RepositorySourceException, InterruptedException;
 
     /**
+     * Execute a command to get the properties and children of a node. {@link GetNodeCommand} is a subtype of both
+     * {@link GetPropertiesCommand} and {@link GetChildrenCommand}, so this method will be called in place of the
+     * {@link #execute(GetPropertiesCommand)} and {@link #execute(GetChildrenCommand)} methods.
+     * 
+     * @param command the command to be executed; may not be null
+     * @throws RepositorySourceException if there is an error executing the command
+     * @throws InterruptedException if the thread is interrupted during execution
+     */
+    void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException;
+
+    /**
      * Execute a command to get the properties of a node.
      * 
      * @param command the command to be executed; may not be null

Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/DelegatingCommandExecutor.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/DelegatingCommandExecutor.java	                        (rev 0)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/DelegatingCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,167 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi.graph.commands.executor;
+
+import org.jboss.dna.spi.graph.commands.CompositeCommand;
+import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
+import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
+import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
+import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
+import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
+import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+public class DelegatingCommandExecutor implements CommandExecutor {
+
+    private final CommandExecutor delegate;
+
+    public DelegatingCommandExecutor( CommandExecutor delegate ) {
+        assert delegate != null;
+        this.delegate = delegate;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#close()
+     */
+    public void close() throws InterruptedException {
+        delegate.close();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GraphCommand)
+     */
+    public void execute( GraphCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.CompositeCommand)
+     */
+    public void execute( CompositeCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
+     */
+    public void execute( GetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetChildrenCommand)
+     */
+    public void execute( GetChildrenCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.CreateNodeCommand)
+     */
+    public void execute( CreateNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.SetPropertiesCommand)
+     */
+    public void execute( SetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyNodeCommand)
+     */
+    public void execute( CopyNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyBranchCommand)
+     */
+    public void execute( CopyBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.RecordBranchCommand)
+     */
+    public void execute( RecordBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.DeleteBranchCommand)
+     */
+    public void execute( DeleteBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.CommandExecutor#execute(org.jboss.dna.spi.graph.commands.MoveBranchCommand)
+     */
+    public void execute( MoveBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        delegate.execute(command);
+    }
+
+}

Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/LoggingCommandExecutor.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/LoggingCommandExecutor.java	                        (rev 0)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/LoggingCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,233 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi.graph.commands.executor;
+
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.spi.SpiI18n;
+import org.jboss.dna.spi.graph.commands.CompositeCommand;
+import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
+import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
+import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
+import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
+import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
+import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+public class LoggingCommandExecutor extends DelegatingCommandExecutor {
+
+    private final Logger logger;
+    private final Logger.Level level;
+
+    /**
+     * Create a command executor that logs before and after each method call, logging messages at the {@link Logger.Level#TRACE
+     * trace} level.
+     * 
+     * @param delegate the delegate executor
+     * @param logger the logger
+     */
+    public LoggingCommandExecutor( CommandExecutor delegate,
+                                   Logger logger ) {
+        this(delegate, logger, Logger.Level.TRACE);
+    }
+
+    /**
+     * Create a command executor that logs before and after each method call, logging messages at the supplied
+     * {@link Logger.Level level}.
+     * 
+     * @param delegate the delegate executor
+     * @param logger the logger
+     * @param level the logging level, or null if {@link Logger.Level#TRACE trace-level} logging should be used.
+     */
+    public LoggingCommandExecutor( CommandExecutor delegate,
+                                   Logger logger,
+                                   Logger.Level level ) {
+        super(delegate);
+        assert logger != null;
+        this.logger = logger;
+        this.level = level != null ? level : Logger.Level.TRACE;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#close()
+     */
+    @Override
+    public void close() throws InterruptedException {
+        this.logger.log(level, SpiI18n.closingCommandExecutor);
+        super.close();
+        this.logger.log(level, SpiI18n.closedCommandExecutor);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CompositeCommand)
+     */
+    @Override
+    public void execute( CompositeCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyBranchCommand)
+     */
+    @Override
+    public void execute( CopyBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyNodeCommand)
+     */
+    @Override
+    public void execute( CopyNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CreateNodeCommand)
+     */
+    @Override
+    public void execute( CreateNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.DeleteBranchCommand)
+     */
+    @Override
+    public void execute( DeleteBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetChildrenCommand)
+     */
+    @Override
+    public void execute( GetChildrenCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    @Override
+    public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
+     */
+    @Override
+    public void execute( GetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GraphCommand)
+     */
+    @Override
+    public void execute( GraphCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.MoveBranchCommand)
+     */
+    @Override
+    public void execute( MoveBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.RecordBranchCommand)
+     */
+    @Override
+    public void execute( RecordBranchCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.DelegatingCommandExecutor#execute(org.jboss.dna.spi.graph.commands.SetPropertiesCommand)
+     */
+    @Override
+    public void execute( SetPropertiesCommand command ) throws RepositorySourceException, InterruptedException {
+        this.logger.log(level, SpiI18n.executingGraphCommand, command);
+        super.execute(command);
+        this.logger.log(level, SpiI18n.executedGraphCommand, command);
+    }
+
+}

Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/NoOpCommandExecutor.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/NoOpCommandExecutor.java	                        (rev 0)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/executor/NoOpCommandExecutor.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,189 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi.graph.commands.executor;
+
+import org.jboss.dna.spi.graph.DateTime;
+import org.jboss.dna.spi.graph.commands.CompositeCommand;
+import org.jboss.dna.spi.graph.commands.CopyBranchCommand;
+import org.jboss.dna.spi.graph.commands.CopyNodeCommand;
+import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
+import org.jboss.dna.spi.graph.commands.DeleteBranchCommand;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetNodeCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
+import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
+import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+
+/**
+ * @author Randall Hauch
+ */
+public class NoOpCommandExecutor extends AbstractCommandExecutor {
+
+    /**
+     * Create a command executor that does nothing.
+     * 
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     */
+    public NoOpCommandExecutor( ExecutionEnvironment env,
+                                String sourceName ) {
+        super(env, sourceName);
+    }
+
+    /**
+     * Create a command executor that does nothing.
+     * 
+     * @param env the execution environment in which the executor will be run; may not be null
+     * @param sourceName the name of the {@link RepositorySource} that is making use of this executor; may not be null or empty
+     * @param now the current time; may be null if the system time is to be used
+     */
+    public NoOpCommandExecutor( ExecutionEnvironment env,
+                                String sourceName,
+                                DateTime now ) {
+        super(env, sourceName, now);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GraphCommand)
+     */
+    @Override
+    public void execute( GraphCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CompositeCommand)
+     */
+    @Override
+    public void execute( CompositeCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetNodeCommand)
+     */
+    @Override
+    public void execute( GetNodeCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyBranchCommand)
+     */
+    @Override
+    public void execute( CopyBranchCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CopyNodeCommand)
+     */
+    @Override
+    public void execute( CopyNodeCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.CreateNodeCommand)
+     */
+    @Override
+    public void execute( CreateNodeCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.DeleteBranchCommand)
+     */
+    @Override
+    public void execute( DeleteBranchCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetChildrenCommand)
+     */
+    @Override
+    public void execute( GetChildrenCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.GetPropertiesCommand)
+     */
+    @Override
+    public void execute( GetPropertiesCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.MoveBranchCommand)
+     */
+    @Override
+    public void execute( MoveBranchCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.RecordBranchCommand)
+     */
+    @Override
+    public void execute( RecordBranchCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.commands.executor.AbstractCommandExecutor#execute(org.jboss.dna.spi.graph.commands.SetPropertiesCommand)
+     */
+    @Override
+    public void execute( SetPropertiesCommand command ) throws RepositorySourceException {
+        // do nothing
+    }
+}

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCompositeCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCompositeCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCompositeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -68,4 +68,14 @@
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + " (containing " + commands.size() + " commands)";
+    }
+
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCopyNodeCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCopyNodeCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCopyNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -38,7 +38,8 @@
      * @param oldPath the path to the original; may not be null
      * @param newPath the path to the copy; may not be null
      */
-    public BasicCopyNodeCommand( Path oldPath, Path newPath ) {
+    public BasicCopyNodeCommand( Path oldPath,
+                                 Path newPath ) {
         super();
         assert oldPath != null;
         assert newPath != null;
@@ -60,4 +61,14 @@
         return newPath;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + " from " + this.getPath() + " to " + this.getNewPath();
+    }
+
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCreateNodeCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCreateNodeCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicCreateNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -23,6 +23,7 @@
 
 import java.util.List;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.spi.graph.Path;
 import org.jboss.dna.spi.graph.Property;
 import org.jboss.dna.spi.graph.commands.CreateNodeCommand;
@@ -84,7 +85,7 @@
      */
     public int compareTo( CreateNodeCommand that ) {
         if (this == that) return 0;
-        return this.path.compareTo(that.getPath());
+        return this.getPath().compareTo(that.getPath());
     }
 
     /**
@@ -107,4 +108,38 @@
         }
         return false;
     }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName());
+        sb.append(" at ");
+        sb.append(this.getPath());
+        boolean firstProperty = true;
+        for (Property property : this.getProperties()) {
+            if (property.isEmpty()) continue;
+            if (firstProperty) {
+                sb.append(" { ");
+                firstProperty = false;
+            } else {
+                sb.append("; ");
+            }
+            sb.append(property.getName());
+            sb.append("=");
+            if (property.isSingle()) {
+                sb.append(StringUtil.readableString(property.getValues().next()));
+            } else {
+                sb.append(StringUtil.readableString(property.getValuesAsArray()));
+            }
+        }
+        if (!firstProperty) {
+            sb.append(" }");
+        }
+        return sb.toString();
+    }
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicDeleteBranchCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicDeleteBranchCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicDeleteBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -49,4 +49,13 @@
         return path;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + " at " + this.getPath();
+    }
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetChildrenCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetChildrenCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetChildrenCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -25,6 +25,7 @@
 import java.util.Iterator;
 import java.util.List;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.spi.cache.CachePolicy;
 import org.jboss.dna.spi.graph.Name;
 import org.jboss.dna.spi.graph.Path;
@@ -131,4 +132,22 @@
         this.cachePolicy = cachePolicy;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName());
+        sb.append(" at ");
+        sb.append(this.getPath());
+        List<Path.Segment> children = this.getChildren();
+        if (children != null && children.size() > 0) {
+            sb.append(" with ").append(children.size()).append(" children: ");
+            sb.append(StringUtil.readableString(children));
+        }
+        return sb.toString();
+    }
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetNodeCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetNodeCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetNodeCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -26,8 +26,10 @@
 import java.util.Iterator;
 import java.util.List;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.spi.graph.Name;
 import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
 import org.jboss.dna.spi.graph.Path.Segment;
 import org.jboss.dna.spi.graph.commands.GetNodeCommand;
 import org.jboss.dna.spi.graph.impl.BasicPathSegment;
@@ -52,6 +54,13 @@
     }
 
     /**
+     * @return children
+     */
+    public List<Segment> getChildren() {
+        return children;
+    }
+
+    /**
      * {@inheritDoc}
      */
     public void setChild( Name nameOfChild ) {
@@ -111,4 +120,43 @@
         children = Collections.emptyList();
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName());
+        sb.append(" at ");
+        sb.append(this.getPath());
+        boolean firstProperty = true;
+        for (Property property : this.getPropertyIterator()) {
+            if (property.isEmpty()) continue;
+            if (firstProperty) {
+                sb.append(" { ");
+                firstProperty = false;
+            } else {
+                sb.append("; ");
+            }
+            sb.append(property.getName());
+            sb.append("=");
+            if (property.isSingle()) {
+                sb.append(StringUtil.readableString(property.getValues().next()));
+            } else {
+                sb.append(StringUtil.readableString(property.getValuesAsArray()));
+            }
+        }
+        if (!firstProperty) {
+            sb.append(" }");
+        }
+        List<Path.Segment> children = this.getChildren();
+        if (children != null && children.size() > 0) {
+            sb.append(" with ").append(children.size()).append(" children: ");
+            sb.append(StringUtil.readableString(children));
+        }
+        return sb.toString();
+    }
+
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetPropertiesCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetPropertiesCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicGetPropertiesCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -24,6 +24,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.spi.cache.CachePolicy;
 import org.jboss.dna.spi.graph.Name;
 import org.jboss.dna.spi.graph.Path;
@@ -110,4 +111,37 @@
         this.cachePolicy = cachePolicy;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName());
+        sb.append(" at ");
+        sb.append(this.getPath());
+        boolean firstProperty = true;
+        for (Property property : this.getPropertyIterator()) {
+            if (property.isEmpty()) continue;
+            if (firstProperty) {
+                sb.append(" { ");
+                firstProperty = false;
+            } else {
+                sb.append("; ");
+            }
+            sb.append(property.getName());
+            sb.append("=");
+            if (property.isSingle()) {
+                sb.append(StringUtil.readableString(property.getValues().next()));
+            } else {
+                sb.append(StringUtil.readableString(property.getValuesAsArray()));
+            }
+        }
+        if (!firstProperty) {
+            sb.append(" }");
+        }
+        return sb.toString();
+    }
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicMoveBranchCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicMoveBranchCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicMoveBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -74,4 +74,14 @@
         return conflictBehavior;
     }
 
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + " from " + this.getPath() + " to " + this.getNewPath();
+    }
+
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicRecordBranchCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicRecordBranchCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicRecordBranchCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -167,8 +167,8 @@
      * Method that is called whenver a node is recorded by the recipient of this command. This implementation simply records it,
      * but subclasses can override this method to respond immediately.
      * 
-     * @param command the command containing the node information; never null and always above the
-     *        {@link #getMaxDepth() maximum depth}.
+     * @param command the command containing the node information; never null and always above the {@link #getMaxDepth() maximum
+     *        depth}.
      */
     protected void record( CreateNodeCommand command ) {
         commands.add(command);
@@ -182,4 +182,15 @@
     public List<CreateNodeCommand> getCreateNodeCommands() {
         return this.commands;
     }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + " at " + this.getPath();
+    }
+
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicSetPropertiesCommand.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicSetPropertiesCommand.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/commands/impl/BasicSetPropertiesCommand.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -23,6 +23,7 @@
 
 import java.util.List;
 import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.StringUtil;
 import org.jboss.dna.spi.graph.Path;
 import org.jboss.dna.spi.graph.Property;
 import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
@@ -65,4 +66,38 @@
     public Iterable<Property> getProperties() {
         return properties;
     }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(this.getClass().getSimpleName());
+        sb.append(" at ");
+        sb.append(this.getPath());
+        boolean firstProperty = true;
+        for (Property property : this.getProperties()) {
+            if (property.isEmpty()) continue;
+            if (firstProperty) {
+                sb.append(" { ");
+                firstProperty = false;
+            } else {
+                sb.append("; ");
+            }
+            sb.append(property.getName());
+            sb.append("=");
+            if (property.isSingle()) {
+                sb.append(StringUtil.readableString(property.getValues().next()));
+            } else {
+                sb.append(StringUtil.readableString(property.getValuesAsArray()));
+            }
+        }
+        if (!firstProperty) {
+            sb.append(" }");
+        }
+        return sb.toString();
+    }
 }

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/BasicPath.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/BasicPath.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/BasicPath.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -307,7 +307,41 @@
 
     /**
      * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.Path#isAtOrBelow(org.jboss.dna.spi.graph.Path)
      */
+    public boolean isAtOrBelow( Path other ) {
+        if (other == null) return false;
+        if (this == other) return true;
+        Iterator<Segment> thisIter = this.segments.iterator();
+        Iterator<Segment> thatIter = other.iterator();
+        while (thisIter.hasNext() && thatIter.hasNext()) {
+            if (!thisIter.next().equals(thatIter.next())) return false;
+        }
+        if (thatIter.hasNext()) return false; // The other still has segments, but this doesn't
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.Path#isAtOrAbove(org.jboss.dna.spi.graph.Path)
+     */
+    public boolean isAtOrAbove( Path other ) {
+        if (other == null) return false;
+        if (this == other) return true;
+        Iterator<Segment> thisIter = this.segments.iterator();
+        Iterator<Segment> thatIter = other.iterator();
+        while (thisIter.hasNext() && thatIter.hasNext()) {
+            if (!thisIter.next().equals(thatIter.next())) return false;
+        }
+        if (thisIter.hasNext()) return false; // This still has segments, but other doesn't
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public boolean isDecendantOf( Path ancestor ) {
         if (ancestor == null) return false;
         return ancestor.isAncestorOf(this);
@@ -330,8 +364,8 @@
     /**
      * {@inheritDoc}
      */
-    public boolean isSame( Path other ) {
-        return this.compareTo(other) == 0;
+    public boolean isSameAs( Path other ) {
+        return other != null && this.compareTo(other) == 0;
     }
 
     /**

Modified: trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/PathValueFactory.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/PathValueFactory.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/java/org/jboss/dna/spi/graph/impl/PathValueFactory.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -395,7 +395,19 @@
 
     /**
      * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.PathFactory#create(org.jboss.dna.spi.graph.Path, java.lang.String)
      */
+    public Path create( Path parentPath,
+                        String subpath ) {
+        // Create a relative path for the subpath ...
+        Path relativeSubpath = create(subpath);
+        return create(parentPath, relativeSubpath);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public Segment createSegment( Name segmentName ) {
         ArgCheck.isNotNull(segmentName, "segment name");
         if (Path.SELF_NAME.equals(segmentName)) return Path.SELF_SEGMENT;

Modified: trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties
===================================================================
--- trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/main/resources/org/jboss/dna/spi/SpiI18n.properties	2008-07-24 19:52:13 UTC (rev 369)
@@ -45,3 +45,8 @@
 pathExpressionIsInvalid = The path expression {0} is not valid
 pathExpressionHasInvalidSelect = Invalid select expression "{0}" in the path expression "{1}"
 pathExpressionHasInvalidMatch = Invalid match expression "{0}" in the path expression "{1}"
+
+executingGraphCommand = Executing {0}
+executedGraphCommand = Executed {0}
+closingCommandExecutor = Closing command executor
+closedCommandExecutor = Closed command executor

Modified: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutorTest.java
===================================================================
--- trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutorTest.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/commands/executor/AbstractCommandExecutorTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -44,6 +44,7 @@
 import org.jboss.dna.spi.graph.commands.MoveBranchCommand;
 import org.jboss.dna.spi.graph.commands.RecordBranchCommand;
 import org.jboss.dna.spi.graph.commands.SetPropertiesCommand;
+import org.jboss.dna.spi.graph.connection.BasicExecutionEnvironment;
 import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
 import org.jboss.dna.spi.graph.connection.RepositorySourceException;
 import org.junit.Before;
@@ -61,7 +62,6 @@
 
     private AbstractCommandExecutor executor;
     private GraphCommand command;
-    @Mock
     private ExecutionEnvironment env;
     @Mock
     protected CommandExecutor validator;
@@ -69,6 +69,7 @@
     @Before
     public void beforeEach() throws Exception {
         MockitoAnnotations.initMocks(this);
+        env = new BasicExecutionEnvironment();
         executor = new ExecutorImpl(env, "Source X", validator);
     }
 
@@ -123,6 +124,7 @@
         verify(validator, times(2)).execute((GetPropertiesCommand)getNodeCommand);
         verify(validator, times(2)).execute((GetChildrenCommand)getNodeCommand);
         verify(validator, times(1)).execute(createNodeCommand);
+        verify(validator, times(2)).execute(getNodeCommand);
         verifyNoMoreInteractions(validator);
     }
 
@@ -139,6 +141,7 @@
         verify(validator, times(2)).execute((GetPropertiesCommand)getNodeCommand);
         verify(validator, times(2)).execute((GetChildrenCommand)getNodeCommand);
         verify(validator, times(1)).execute(createNodeCommand);
+        verify(validator, times(2)).execute(getNodeCommand);
         verifyNoMoreInteractions(validator);
     }
 
@@ -149,6 +152,7 @@
         verify(validator, times(1)).execute((GraphCommand)getNodeCommand);
         verify(validator, times(1)).execute((GetPropertiesCommand)getNodeCommand);
         verify(validator, times(1)).execute((GetChildrenCommand)getNodeCommand);
+        verify(validator, times(1)).execute(getNodeCommand);
         verifyNoMoreInteractions(validator);
     }
 
@@ -265,6 +269,11 @@
         }
 
         @Override
+        public void execute( GetNodeCommand command ) throws RepositorySourceException, InterruptedException {
+            validator.execute(command);
+        }
+
+        @Override
         public void execute( RecordBranchCommand command ) throws RepositorySourceException, InterruptedException {
             validator.execute(command);
         }

Copied: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepository.java (from rev 337, trunk/dna-repository/src/test/java/org/jboss/dna/repository/federation/SimpleRepository.java)
===================================================================
--- trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepository.java	                        (rev 0)
+++ trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepository.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,178 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi.graph.connection;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.NameFactory;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.PathFactory;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.PropertyFactory;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+
+/**
+ * A very simple repository that maintains properties for nodes identified by a path, and computes the children based upon the set
+ * of paths registered in the {@link #getData() data}.
+ * <p>
+ * Note that the repository does not automatically rename same-name siblings when nodes are
+ * {@link #delete(ExecutionEnvironment, String) deleted} or {@link #create(ExecutionEnvironment, String) explicitly} or
+ * {@link #setProperty(ExecutionEnvironment, String, String, Object...) implicitly} created.
+ * </p>
+ * 
+ * @author Randall Hauch
+ */
+ at ThreadSafe
+public class SimpleRepository {
+
+    private static final ConcurrentMap<String, SimpleRepository> repositoriesByName = new ConcurrentHashMap<String, SimpleRepository>();
+
+    public static SimpleRepository get( String name ) {
+        return repositoriesByName.get(name);
+    }
+
+    public static void shutdownAll() {
+        for (SimpleRepository repository : repositoriesByName.values()) {
+            repository.shutdown();
+        }
+    }
+
+    private ConcurrentMap<Path, Map<Name, Property>> data = new ConcurrentHashMap<Path, Map<Name, Property>>();
+    private final String repositoryName;
+    private boolean shutdown = false;
+
+    public SimpleRepository( String repositoryName ) {
+        this.repositoryName = repositoryName;
+        if (repositoriesByName.putIfAbsent(repositoryName, this) != null) {
+            throw new IllegalArgumentException("Repository \"" + repositoryName + "\" already exists and may not be recreated");
+        }
+    }
+
+    /**
+     * @return repositoryName
+     */
+    public String getRepositoryName() {
+        return repositoryName;
+    }
+
+    /**
+     * Get the current modifiable map of property data
+     * 
+     * @return data
+     */
+    public ConcurrentMap<Path, Map<Name, Property>> getData() {
+        return data;
+    }
+
+    /**
+     * Utility method to help set the property on the node given by the supplied path. If the node does not exist, it will be
+     * created.
+     * 
+     * @param env the environment; may not be null
+     * @param path the path to the node; may not be null
+     * @param propertyName the property name; may not be null
+     * @param values the values of the property
+     * @return this repository, for method chaining
+     */
+    public SimpleRepository setProperty( ExecutionEnvironment env,
+                                         String path,
+                                         String propertyName,
+                                         Object... values ) {
+        PathFactory pathFactory = env.getValueFactories().getPathFactory();
+        NameFactory nameFactory = env.getValueFactories().getNameFactory();
+        PropertyFactory propertyFactory = env.getPropertyFactory();
+        Path pathObj = pathFactory.create(path);
+        create(env, pathObj.getAncestor().getString(env.getNamespaceRegistry()));
+        Property property = propertyFactory.create(nameFactory.create(propertyName), values);
+        Map<Name, Property> properties = new HashMap<Name, Property>();
+        Map<Name, Property> existingProperties = data.putIfAbsent(pathObj, properties);
+        if (existingProperties == null) existingProperties = properties;
+        existingProperties.put(property.getName(), property);
+        return this;
+    }
+
+    /**
+     * Create the node if it does not exist.
+     * 
+     * @param env the environment; may not be null
+     * @param path the path to the node; may not be null
+     * @return this repository, for method chaining
+     */
+    public SimpleRepository create( ExecutionEnvironment env,
+                                    String path ) {
+        PathFactory pathFactory = env.getValueFactories().getPathFactory();
+        Path pathObj = pathFactory.create(path);
+        Path ancestorPath = pathObj.getAncestor();
+        while (!ancestorPath.isRoot()) {
+            data.putIfAbsent(ancestorPath, new HashMap<Name, Property>());
+            ancestorPath = ancestorPath.getAncestor();
+        }
+        data.putIfAbsent(pathObj, new HashMap<Name, Property>());
+        return this;
+    }
+
+    /**
+     * Delete the branch rooted at the supplied path, if it exists.
+     * 
+     * @param env the environment; may not be null
+     * @param path the path to the branch's top node; may not be null
+     * @return this repository, for method chaining
+     */
+    public SimpleRepository delete( ExecutionEnvironment env,
+                                    String path ) {
+        PathFactory pathFactory = env.getValueFactories().getPathFactory();
+        Path pathObj = pathFactory.create(path);
+        List<Path> pathsToRemove = new LinkedList<Path>();
+        for (Path nodePath : data.keySet()) {
+            if (nodePath.equals(pathObj) || nodePath.isDecendantOf(pathObj)) {
+                pathsToRemove.add(nodePath);
+            }
+        }
+        for (Path pathToRemove : pathsToRemove) {
+            data.remove(pathToRemove);
+        }
+        return this;
+    }
+
+    /**
+     * @param data new new map of property data
+     */
+    public void setData( ConcurrentMap<Path, Map<Name, Property>> data ) {
+        this.data = data;
+    }
+
+    public boolean isShutdown() {
+        return shutdown;
+    }
+
+    public void shutdown() {
+        shutdown = true;
+        repositoriesByName.remove(this.repositoryName);
+    }
+
+}


Property changes on: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepository.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Copied: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepositorySource.java (from rev 337, trunk/dna-repository/src/test/java/org/jboss/dna/repository/federation/SimpleRepositorySource.java)
===================================================================
--- trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepositorySource.java	                        (rev 0)
+++ trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepositorySource.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -0,0 +1,294 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors. 
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.spi.graph.connection;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.naming.Reference;
+import javax.transaction.xa.XAResource;
+import org.jboss.dna.spi.cache.CachePolicy;
+import org.jboss.dna.spi.graph.InvalidPathException;
+import org.jboss.dna.spi.graph.Name;
+import org.jboss.dna.spi.graph.Path;
+import org.jboss.dna.spi.graph.Property;
+import org.jboss.dna.spi.graph.commands.CompositeCommand;
+import org.jboss.dna.spi.graph.commands.GetChildrenCommand;
+import org.jboss.dna.spi.graph.commands.GetPropertiesCommand;
+import org.jboss.dna.spi.graph.commands.GraphCommand;
+import org.jboss.dna.spi.graph.connection.ExecutionEnvironment;
+import org.jboss.dna.spi.graph.connection.RepositoryConnection;
+import org.jboss.dna.spi.graph.connection.RepositorySource;
+import org.jboss.dna.spi.graph.connection.RepositorySourceException;
+import org.jboss.dna.spi.graph.connection.RepositorySourceListener;
+
+/**
+ * A {@link RepositorySource} for a {@link SimpleRepository simple repository}.
+ * 
+ * @author Randall Hauch
+ */
+public class SimpleRepositorySource implements RepositorySource {
+
+    private static final long serialVersionUID = 1L;
+
+    public static final int DEFAULT_RETRY_LIMIT = 5;
+
+    private String repositoryName;
+    private String name;
+    private int retryLimit = DEFAULT_RETRY_LIMIT;
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.connection.RepositorySource#getRetryLimit()
+     */
+    public int getRetryLimit() {
+        return retryLimit;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.connection.RepositorySource#setRetryLimit(int)
+     */
+    public void setRetryLimit( int limit ) {
+        retryLimit = limit;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory#getName()
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name Sets name to the specified value.
+     */
+    public void setName( String name ) {
+        this.name = name;
+    }
+
+    /**
+     * @return repositoryName
+     */
+    public String getRepositoryName() {
+        return repositoryName;
+    }
+
+    /**
+     * @param repositoryName Sets repositoryName to the specified value.
+     */
+    public void setRepositoryName( String repositoryName ) {
+        this.repositoryName = repositoryName;
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see javax.naming.Referenceable#getReference()
+     */
+    public Reference getReference() {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see org.jboss.dna.spi.graph.connection.RepositoryConnectionFactory#getConnection()
+     */
+    public RepositoryConnection getConnection() throws RepositorySourceException {
+        String reposName = this.repositoryName;
+        SimpleRepository repository = SimpleRepository.get(reposName);
+        if (repository == null) {
+            throw new RepositorySourceException(this.getName(), "Unable to find repository \"" + reposName + "\"");
+        }
+        return new Connection(repository);
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return name.hashCode();
+    }
+
+    /**
+     * {@inheritDoc}
+     * 
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals( Object obj ) {
+        if (obj == this) return true;
+        if (obj instanceof SimpleRepositorySource) {
+            SimpleRepositorySource that = (SimpleRepositorySource)obj;
+            if (!this.getName().equals(that.getName())) return false;
+            if (!this.getRepositoryName().equals(that.getRepositoryName())) return false;
+            return true;
+        }
+        return false;
+    }
+
+    protected class Connection implements RepositoryConnection {
+
+        private RepositorySourceListener listener;
+        private final SimpleRepository repository;
+
+        protected Connection( SimpleRepository repository ) {
+            assert repository != null;
+            this.repository = repository;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#close()
+         */
+        public void close() {
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#execute(org.jboss.dna.spi.graph.connection.ExecutionEnvironment,
+         *      org.jboss.dna.spi.graph.commands.GraphCommand[])
+         */
+        public void execute( ExecutionEnvironment env,
+                             GraphCommand... commands ) throws RepositorySourceException {
+            assert env != null;
+            if (repository.isShutdown()) {
+                throw new RepositorySourceException(getName(), "The repository \"" + repository.getRepositoryName()
+                                                               + "\" is no longer available");
+            }
+            for (GraphCommand command : commands) {
+                executeCommand(env, command);
+            }
+        }
+
+        protected void executeCommand( ExecutionEnvironment env,
+                                       GraphCommand command ) {
+            if (command == null) return;
+            if (command instanceof CompositeCommand) {
+                CompositeCommand composite = (CompositeCommand)command;
+                for (GraphCommand nestedCommand : composite) {
+                    executeCommand(env, nestedCommand);
+                }
+            }
+            Map<Path, Map<Name, Property>> data = repository.getData();
+            // Only commands we recognize are the following; everything else is ignored ...
+            if (command instanceof GetPropertiesCommand) { // super of GetNodeCommand
+                GetPropertiesCommand getProperties = (GetPropertiesCommand)command;
+                Path targetPath = getProperties.getPath();
+                Map<Name, Property> properties = data.get(targetPath);
+                if (properties == null) {
+                    getProperties.setError(new InvalidPathException("Non-existant node: " + targetPath));
+                    return;
+                }
+                for (Property property : properties.values()) {
+                    getProperties.setProperty(property);
+                }
+            }
+            if (command instanceof GetChildrenCommand) { // super of GetNodeCommand
+                GetChildrenCommand getChildren = (GetChildrenCommand)command;
+                Path targetPath = getChildren.getPath();
+                if (data.get(targetPath) == null) {
+                    getChildren.setError(new InvalidPathException("Non-existant node: " + targetPath));
+                    return;
+                }
+                // Iterate through all of the properties, looking for any paths that are children of the path ...
+                List<Path.Segment> childSegments = new LinkedList<Path.Segment>();
+                for (Path path : data.keySet()) {
+                    if (path.getAncestor().equals(targetPath)) {
+                        childSegments.add(path.getLastSegment());
+                    }
+                }
+                // This does not store children order, so sort ...
+                Collections.sort(childSegments);
+                getChildren.setChildren(childSegments);
+            }
+
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#getDefaultCachePolicy()
+         */
+        public CachePolicy getDefaultCachePolicy() {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#getSourceName()
+         */
+        public String getSourceName() {
+            return SimpleRepositorySource.this.getName();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#getXAResource()
+         */
+        public XAResource getXAResource() {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#ping(long, java.util.concurrent.TimeUnit)
+         */
+        public boolean ping( long time,
+                             TimeUnit unit ) {
+            return true;
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.jboss.dna.spi.graph.connection.RepositoryConnection#setListener(org.jboss.dna.spi.graph.connection.RepositorySourceListener)
+         */
+        public void setListener( RepositorySourceListener listener ) {
+            this.listener = listener;
+        }
+
+        /**
+         * @return listener
+         */
+        public RepositorySourceListener getListener() {
+            return listener;
+        }
+
+    }
+
+}


Property changes on: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/connection/SimpleRepositorySource.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/BasicPathTest.java
===================================================================
--- trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/BasicPathTest.java	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/dna-spi/src/test/java/org/jboss/dna/spi/graph/impl/BasicPathTest.java	2008-07-24 19:52:13 UTC (rev 369)
@@ -409,6 +409,96 @@
     }
 
     @Test
+    public void shouldConsiderNodeToBeAtOrAboveItself() {
+        assertThat(path.isAtOrAbove(path), is(true));
+    }
+
+    @Test
+    public void shouldConsiderNodeToBeAtOrBelowItself() {
+        assertThat(path.isAtOrBelow(path), is(true));
+    }
+
+    @Test
+    public void shouldConsiderAncestorToBeAtOrAboveTheDecendant() {
+        Path path1 = pathFactory.create("/a/y/z");
+        Path path2 = pathFactory.create("/a/b/c");
+        Path path3 = pathFactory.create("/x/b/c");
+        Path path4 = pathFactory.create("/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z");
+        for (int i = 1; i < path1.size(); ++i) {
+            assertThat(path1.getAncestor(i).isAtOrAbove(path1), is(true));
+        }
+        for (int i = 1; i < path2.size(); ++i) {
+            assertThat(path2.getAncestor(i).isAtOrAbove(path2), is(true));
+        }
+        for (int i = 1; i < path3.size(); ++i) {
+            assertThat(path3.getAncestor(i).isAtOrAbove(path3), is(true));
+        }
+        for (int i = 1; i < path4.size(); ++i) {
+            assertThat(path4.getAncestor(i).isAtOrAbove(path4), is(true));
+        }
+    }
+
+    @Test
+    public void shouldConsiderDecendantToBeAtOrBelowTheAncestor() {
+        Path path1 = pathFactory.create("/a/y/z");
+        Path path2 = pathFactory.create("/a/b/c");
+        Path path3 = pathFactory.create("/x/b/c");
+        Path path4 = pathFactory.create("/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z");
+        for (int i = 1; i < path1.size(); ++i) {
+            assertThat(path1.isAtOrBelow(path1.getAncestor(i)), is(true));
+        }
+        for (int i = 1; i < path2.size(); ++i) {
+            assertThat(path2.isAtOrBelow(path2.getAncestor(i)), is(true));
+        }
+        for (int i = 1; i < path3.size(); ++i) {
+            assertThat(path3.isAtOrBelow(path3.getAncestor(i)), is(true));
+        }
+        for (int i = 1; i < path4.size(); ++i) {
+            assertThat(path4.isAtOrBelow(path4.getAncestor(i)), is(true));
+        }
+    }
+
+    @Test
+    public void shouldNotConsiderAncestorToBeAtOrBelowTheDecendant() {
+        Path path1 = pathFactory.create("/a/y/z");
+        Path path2 = pathFactory.create("/a/b/c");
+        Path path3 = pathFactory.create("/x/b/c");
+        Path path4 = pathFactory.create("/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z");
+        for (int i = 1; i < path1.size(); ++i) {
+            assertThat(path1.getAncestor(i).isAtOrBelow(path1), is(false));
+        }
+        for (int i = 1; i < path2.size(); ++i) {
+            assertThat(path2.getAncestor(i).isAtOrBelow(path2), is(false));
+        }
+        for (int i = 1; i < path3.size(); ++i) {
+            assertThat(path3.getAncestor(i).isAtOrBelow(path3), is(false));
+        }
+        for (int i = 1; i < path4.size(); ++i) {
+            assertThat(path4.getAncestor(i).isAtOrBelow(path4), is(false));
+        }
+    }
+
+    @Test
+    public void shouldNotConsiderDecendantToBeAtOrAboveTheAncestor() {
+        Path path1 = pathFactory.create("/a/y/z");
+        Path path2 = pathFactory.create("/a/b/c");
+        Path path3 = pathFactory.create("/x/b/c");
+        Path path4 = pathFactory.create("/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z");
+        for (int i = 1; i < path1.size(); ++i) {
+            assertThat(path1.isAtOrAbove(path1.getAncestor(i)), is(false));
+        }
+        for (int i = 1; i < path2.size(); ++i) {
+            assertThat(path2.isAtOrAbove(path2.getAncestor(i)), is(false));
+        }
+        for (int i = 1; i < path3.size(); ++i) {
+            assertThat(path3.isAtOrAbove(path3.getAncestor(i)), is(false));
+        }
+        for (int i = 1; i < path4.size(); ++i) {
+            assertThat(path4.isAtOrAbove(path4.getAncestor(i)), is(false));
+        }
+    }
+
+    @Test
     public void shouldReturnNullForLastSegmentOfRoot() {
         assertThat(ROOT.getLastSegment(), is(nullValue()));
     }

Modified: trunk/pom.xml
===================================================================
--- trunk/pom.xml	2008-07-23 17:17:39 UTC (rev 368)
+++ trunk/pom.xml	2008-07-24 19:52:13 UTC (rev 369)
@@ -104,6 +104,7 @@
 		<module>sequencers/dna-sequencer-cnd</module>
 		<module>sequencers/dna-sequencer-java</module>
 		<module>sequencers/dna-sequencer-msoffice</module>
+		<module>connectors/dna-connector-federation</module>
 		<module>connectors/dna-connector-inmemory</module>
 		<module>connectors/dna-connector-jbosscache</module>
         <module>extensions/dna-mimetype-detector-aperture</module>
@@ -341,6 +342,21 @@
 				<artifactId>dna-repository</artifactId>
 				<version>${dna-version}</version>
 			</dependency>
+			<dependency>
+				<groupId>org.jboss.dna</groupId>
+				<artifactId>dna-connector-federation</artifactId>
+				<version>${dna-version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.jboss.dna</groupId>
+				<artifactId>dna-connector-jbosscache</artifactId>
+				<version>${dna-version}</version>
+			</dependency>
+			<dependency>
+				<groupId>org.jboss.dna</groupId>
+				<artifactId>dna-connector-inmemory</artifactId>
+				<version>${dna-version}</version>
+			</dependency>
             <dependency>
                 <groupId>org.jboss.dna</groupId>
                 <artifactId>dna-mimetype-detector-aperture</artifactId>




More information about the dna-commits mailing list