Initial Impl of baseline jetty-deploy-manager

git-svn-id: svn+ssh://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/sandbox/trunk@985 7e9141cc-0065-0410-87d8-b60c137991c4
diff --git a/jetty-deploy-manager/.gitignore b/jetty-deploy-manager/.gitignore
new file mode 100644
index 0000000..796b602
--- /dev/null
+++ b/jetty-deploy-manager/.gitignore
@@ -0,0 +1,9 @@
+target/
+.classpath
+.project
+.settings
+*/src/main/java/META-INF/
+*.log
+*.swp
+*.diff
+*.patch
diff --git a/jetty-deploy-manager/pom.xml b/jetty-deploy-manager/pom.xml
new file mode 100644
index 0000000..e905a1d
--- /dev/null
+++ b/jetty-deploy-manager/pom.xml
@@ -0,0 +1,83 @@
+<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">
+  <parent>
+    <groupId>org.eclipse.jetty</groupId>
+    <artifactId>jetty-project</artifactId>
+    <version>7.0.1-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>jetty-deploy-manager</artifactId>
+  <name>Jetty :: Deployment Manager</name>
+  <description>Jetty Deployment Manager</description>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>${felix.bundle.version}</version>
+        <extensions>true</extensions>
+        <executions>
+          <execution>
+            <goals>
+              <goal>manifest</goal>
+            </goals>
+           </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <!--
+        Required for OSGI
+        -->
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <configuration>
+          <archive>
+            <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <!-- always include sources since jetty-xbean makes use of them -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-xml</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.7</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.5</version>
+        <configuration>
+          <docfilessubdirs>true</docfilessubdirs>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/App.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/App.java
new file mode 100644
index 0000000..0fdbd55
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/App.java
@@ -0,0 +1,71 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+/**
+ * The information about an App that is managed by the {@link DeploymentManager}
+ */
+public interface App
+{
+    /**
+     * The raw id of the {@link App}
+     * 
+     * @return the generated Id for the App.
+     */
+    String getId();
+
+    /**
+     * Get the internally declared name of the App.
+     * 
+     * @return the internal name of the App.
+     */
+    String getName();
+
+    /**
+     * Get the desired ContextHandler for the App. (optional)
+     * 
+     * @return the {@link ContextHandler} to use for the App when fully started. (Portions of which might be ignored
+     *         when App is in the {@link AppState#STAGED} state}
+     */
+    ContextHandler getDesiredContextHandler();
+
+    /**
+     * Get the work dir for this App, as managed by the {@link DeploymentManager}
+     * 
+     * @return the App's work dir.
+     */
+    File getWorkDir();
+
+    /**
+     * Get the archive path to the App.
+     * 
+     * Might exist as a Directory (example: an exploded webapp, or a jetty:run) or a File (example: a WAR file)
+     * 
+     * @return the
+     */
+    File getArchivePath();
+
+    /**
+     * The origin of this {@link App} as specified by the {@link AppProvider}
+     * 
+     * @return String representing the origin of this app.
+     */
+    String getOrigin();
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppLifecycle.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppLifecycle.java
new file mode 100644
index 0000000..3ef79d7
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppLifecycle.java
@@ -0,0 +1,358 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * The lifecycle of an App in the {@link DeploymentManager}.
+ * <p>
+ * <img src="doc-files/AppLifecycle.png">
+ */
+public class AppLifecycle
+{
+    static class Edge
+    {
+        private AppState from;
+        private AppState to;
+        private List<AppPhase> phases;
+
+        public Edge(AppState from, AppPhase corePhase, AppState to)
+        {
+            this.from = from;
+            this.to = to;
+
+            this.phases = new ArrayList<AppPhase>();
+
+            // Fun little Enum Hack. ;-)
+            this.phases.add(AppPhase.valueOf("PRE_" + corePhase.name()));
+            this.phases.add(corePhase);
+            this.phases.add(AppPhase.valueOf("POST_" + corePhase.name()));
+        }
+
+        @Override
+        public int hashCode()
+        {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((from == null)?0:from.hashCode());
+            result = prime * result + ((to == null)?0:to.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj)
+        {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            Edge other = (Edge)obj;
+            if (from == null)
+            {
+                if (other.from != null)
+                    return false;
+            }
+            else if (!from.equals(other.from))
+                return false;
+            if (to == null)
+            {
+                if (other.to != null)
+                    return false;
+            }
+            else if (!to.equals(other.to))
+                return false;
+            return true;
+        }
+    }
+
+    class EdgePath implements Iterable<Edge>
+    {
+        private Stack<Edge> path;
+
+        public EdgePath()
+        {
+            path = new Stack<Edge>();
+        }
+
+        public EdgePath forkPath()
+        {
+            EdgePath ep = new EdgePath();
+            for (Edge edge : this)
+            {
+                ep.add(edge);
+            }
+            return ep;
+        }
+
+        private void add(Edge edge)
+        {
+            path.push(edge);
+        }
+
+        public Edge lastEdge()
+        {
+            return path.peek();
+        }
+
+        public int length()
+        {
+            return path.size();
+        }
+
+        public Iterator<Edge> iterator()
+        {
+            return path.iterator();
+        }
+    }
+
+    class EdgeSearch
+    {
+        Set<Edge> seenEdges = new HashSet<Edge>();
+        List<EdgePath> paths = new ArrayList<EdgePath>();
+
+        public EdgeSearch(List<Edge> edgesFrom)
+        {
+            for (Edge from : edgesFrom)
+            {
+                EdgePath path = new EdgePath();
+                path.add(from);
+
+                seenEdges.add(from);
+
+                paths.add(path);
+            }
+        }
+
+        public EdgePath getShortestPath()
+        {
+            EdgePath shortest = null;
+            int shortestlen = Integer.MAX_VALUE;
+
+            for (EdgePath path : paths)
+            {
+                if (shortest == null)
+                {
+                    shortest = path;
+                    continue;
+                }
+
+                int len = path.length();
+
+                if (len < shortestlen)
+                {
+                    shortest = path;
+                    shortestlen = len;
+                }
+            }
+
+            return shortest;
+        }
+
+        public void breadthFirst(AppState destination)
+        {
+            // Test existing edge endpoints
+            if (hasReachedDestination(destination))
+            {
+                // Found our destination!
+
+                // Now remove the other paths that do not end at the destination
+                ListIterator<EdgePath> pathiter = paths.listIterator();
+                while (pathiter.hasNext())
+                {
+                    EdgePath path = pathiter.next();
+                    if (path.lastEdge().to != destination)
+                    {
+                        pathiter.remove();
+                    }
+                }
+                return;
+            }
+
+            List<EdgePath> extrapaths = null;
+
+            // Add next unseen segments to paths.
+            boolean pathsAdded = false;
+
+            for (EdgePath path : paths)
+            {
+                List<Edge> next = nextUnseenEdges(path);
+                if (next.size() == 0)
+                {
+                    continue; // no new edges
+                }
+
+                pathsAdded = true;
+
+                // More than 1 path out? Split it.
+                if (next.size() > 1)
+                {
+                    if (extrapaths == null)
+                    {
+                        extrapaths = new ArrayList<EdgePath>();
+                    }
+
+                    // Split path for other edges
+                    for (int i = 1, n = next.size(); i < n; i++)
+                    {
+                        EdgePath split = path.forkPath();
+                        // Add segment to split'd path
+                        split.add(next.get(i));
+
+                        // Add to extra paths
+                        extrapaths.add(split);
+                    }
+                }
+
+                // Add edge to current path
+                Edge edge = next.get(0);
+                path.add(edge);
+
+                // Mark all edges as seen
+                for (Edge e : next)
+                {
+                    seenEdges.add(e);
+                }
+            }
+
+            // Do we have any extra paths?
+            if (extrapaths != null)
+            {
+                paths.addAll(extrapaths);
+            }
+
+            if (pathsAdded)
+            {
+                // recurse
+                breadthFirst(destination);
+            }
+        }
+
+        private List<Edge> nextUnseenEdges(EdgePath path)
+        {
+            List<Edge> next = new ArrayList<Edge>();
+
+            for (Edge edge : findEdgesFrom(path.lastEdge().to))
+            {
+                if (seenEdges.contains(edge) == false)
+                {
+                    next.add(edge);
+                }
+            }
+
+            return next;
+        }
+
+        private boolean hasReachedDestination(AppState destination)
+        {
+            for (EdgePath path : paths)
+            {
+                if (path.lastEdge().to == destination)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private Set<Edge> edges;
+
+    public AppLifecycle()
+    {
+        // Define Edges
+        // TODO: Have this be specified in a resource file?
+        edges = new HashSet<Edge>();
+        edges.add(new Edge(AppState.UNAVAILABLE,AppPhase.DEPLOY,AppState.DEPLOYED));
+        edges.add(new Edge(AppState.DEPLOYED,AppPhase.STAGE,AppState.STAGED));
+        edges.add(new Edge(AppState.STAGED,AppPhase.START,AppState.STARTED));
+        edges.add(new Edge(AppState.STARTED,AppPhase.STOP,AppState.DEPLOYED));
+        edges.add(new Edge(AppState.STAGED,AppPhase.STOP,AppState.DEPLOYED));
+        edges.add(new Edge(AppState.DEPLOYED,AppPhase.UNDEPLOY,AppState.UNAVAILABLE));
+    }
+
+    public List<AppPhase> getPhases(AppState from, AppState to)
+    {
+        if (from == to)
+        {
+            return Collections.emptyList();
+        }
+
+        // Perform a Breadth First Search (BFS) of the tree.
+        EdgeSearch search = new EdgeSearch(findEdgesFrom(from));
+        search.breadthFirst(to);
+
+        EdgePath path = search.getShortestPath();
+        List<AppPhase> phases = new ArrayList<AppPhase>();
+
+        for (Edge edge : path)
+        {
+            phases.addAll(edge.phases);
+        }
+
+        return phases;
+    }
+
+    private List<Edge> findEdgesFrom(AppState from)
+    {
+        List<Edge> fromedges = new ArrayList<Edge>();
+
+        for (Edge edge : this.edges)
+        {
+            if (edge.from == from)
+            {
+                fromedges.add(edge);
+            }
+        }
+
+        return fromedges;
+    }
+
+    public static void main(String[] args)
+    {
+        // Dump All Paths
+        AppLifecycle lifecycle = new AppLifecycle();
+
+        for (AppState from : AppState.values())
+        {
+            for (AppState to : AppState.values())
+            {
+                System.out.println();
+                System.out.printf("Paths %s -> %s:%n",from,to);
+                List<AppPhase> phases = lifecycle.getPhases(from,to);
+                if (phases.isEmpty())
+                {
+                    System.out.printf("  (no steps needed)%n");
+                    continue;
+                }
+
+                for (AppPhase phase : phases)
+                {
+                    System.out.printf("  %s%n",phase);
+                }
+            }
+        }
+    }
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppPhase.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppPhase.java
new file mode 100644
index 0000000..534350a
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppPhase.java
@@ -0,0 +1,162 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import org.eclipse.jetty.deploy.bindings.DefaultDeployer;
+
+/**
+ * The phases that an App can go through while traversing the {@link AppLifecycle}
+ * <p>
+ * <img src="doc-files/AppLifecycle.png">
+ */
+public enum AppPhase
+{
+    /**
+     * Pre-Deploy Phase.
+     * 
+     * This would be a good place to validate / check / prepare for a deployment.
+     * 
+     * Note: Bound actions can throw an exception here to prevent the {@link #DEPLOY} phase from occurring.
+     */
+    PRE_DEPLOY,
+
+    /**
+     * Perform Deploy Phase.
+     * 
+     * Use this to perform the actual deploy.
+     * 
+     * Success here (no Exception) results in the {@link App} being set to {@link AppState#DEPLOYED}.
+     * 
+     * Bound actions can throw an exception here to indicate a failed deployment.
+     * 
+     * @see {@link DefaultDeployer#handleAppDeploy(App)}
+     */
+    DEPLOY,
+
+    /**
+     * Post-Deploy Phase.
+     * 
+     * Use this to track successfully deployed apps.
+     * 
+     * Note: Exceptions thrown here prevent the next major phase from occurring.
+     * 
+     * Next Phases: {@link #UNDEPLOY} and {@link #STAGE}
+     */
+    POST_DEPLOY,
+
+    /**
+     * Pre-Stage Phase.
+     * 
+     * Use this to prepare any webapp for staging.
+     */
+    PRE_STAGE,
+
+    /**
+     * Perform Stage Phase.
+     * 
+     * Usage ideas: stage a webapp to a temporary context, or even to distribute the webapp across a cluster.
+     * 
+     * Success here (no Exception) results in the {@link App} being set to {@link AppState#STAGED}.
+     * 
+     * Bound actions can throw an exception here to indicate a failed staging
+     */
+    STAGE,
+
+    /**
+     * Post-Stage Phase.
+     * 
+     * Check on a staged deployment.
+     * 
+     * Next Phases: {@link #UNDEPLOY} or {@link #START}
+     */
+    POST_STAGE,
+
+    /**
+     * Pre-Start Phase.
+     * 
+     * Use this to prepare for a webapp that will be started (on its desired context) and becoming available to
+     * requests.
+     */
+    PRE_START,
+
+    /**
+     * Perform Start Phase.
+     * 
+     * Success here (no Exception) results in the {@link App} being set to {@link AppState#STARTED}.
+     * 
+     * Bound actions can throw an exception here to indicate a failed start.
+     */
+    START,
+
+    /**
+     * Post-Start Phase.
+     * 
+     * Check on a started webapp.
+     * 
+     * Next Phases: {@link #STOP}
+     */
+    POST_START,
+
+    /**
+     * Pre-Stop Phase.
+     * 
+     * Use this to prepare for a webapp to be stopped, becoming unvailable for requests.
+     */
+    PRE_STOP,
+
+    /**
+     * Perform Stop Phase.
+     * 
+     * Success here (no Exception) results in the {@link App} being set to {@link AppState#STAGED}.
+     * 
+     * Bound actions can throw an exception here to indicate a failed stop.
+     */
+    STOP,
+
+    /**
+     * Post-Stop Phase.
+     * 
+     * Check on a stopped webapp.
+     * 
+     * Next Phases: {@link #UNDEPLOY} or {@link #STOP}
+     */
+    POST_STOP,
+
+    /**
+     * Pre-Undeploy Phase.
+     * 
+     * Use this to prepare for a webapp being undeployed.
+     */
+    PRE_UNDEPLOY,
+
+    /**
+     * Perform Undeploy Phase.
+     * 
+     * Success here (no Exception) results in the {@link App} being set to {@link AppState#UNAVAILABLE}.
+     * 
+     * Bound actions can throw an exception here to indicate a failed undeploy.
+     */
+    UNDEPLOY,
+
+    /**
+     * Post-Undeploy Phase.
+     * 
+     * Check on a undeployed webapp.
+     * 
+     * Next Phases: None, as app will be considered unavailable.
+     */
+    POST_UNDEPLOY,
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppProvider.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
new file mode 100644
index 0000000..f5ee60b
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
@@ -0,0 +1,26 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+/**
+ * Object responsible for providing {@link App}s to the {@link DeploymentManager}
+ */
+public interface AppProvider
+{
+    void startProvider(AppReceiver receiver);
+
+    void stopProvider();
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppReceiver.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppReceiver.java
new file mode 100644
index 0000000..6abff9a
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppReceiver.java
@@ -0,0 +1,24 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+/**
+ * Interface for {@link AppProvider}s to use to submit new and/or changed {@link App}s to.
+ */
+public interface AppReceiver
+{
+    void receiveApp(App app);
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppState.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppState.java
new file mode 100644
index 0000000..7e85704
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/AppState.java
@@ -0,0 +1,49 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+/**
+ * State that an {@link App} can be in, as a result of traversal via the {@link AppLifecycle}
+ * <p>
+ * <img src="doc-files/AppLifecycle.png">
+ */
+public enum AppState
+{
+    /**
+     * The web app is not available.
+     * 
+     * It has likely not been deployed, or has failed deployment.
+     */
+    UNAVAILABLE,
+
+    /**
+     * The web app has been deployed, but not yet made available for incoming requests.
+     */
+    DEPLOYED,
+
+    /**
+     * The web app has been staged, ready to accept requests, but not on the official context.
+     * 
+     * This can refer to web apps that have been put into a staged context, and also for web apps that have undergone a
+     * distribution accross a cluster of containers.
+     */
+    STAGED,
+
+    /**
+     * The web app has been started and is ready to accept requests on the expected context.
+     */
+    STARTED;
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
new file mode 100644
index 0000000..0b2a87b
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
@@ -0,0 +1,319 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.deploy.annotations.DeployLifecycleBinding;
+
+/**
+ * The main DeploymentManager, responsible for receiving incoming {@link App}, managing {@link AppState}, and providing
+ * an {@link AppPhase} that can be bound to for managing app state transition.
+ */
+public class DeploymentManager implements AppReceiver
+{
+    /**
+     * Represents a single tracked app within the deployment manager.
+     */
+    static class AppEntry
+    {
+        /**
+         * Version of the app.
+         * 
+         * Note: Auto-increments on each {@link AppReceiver#receiveApp(App)}
+         */
+        private int version;
+
+        /**
+         * The app being tracked.
+         */
+        private App app;
+
+        /**
+         * The state of the app.
+         */
+        private AppState state;
+
+        /**
+         * Tracking the various AppState timestamps (in system milliseconds)
+         */
+        private Map<AppState, Long> stateTimestamps = new HashMap<AppState, Long>();
+
+        void updateState(AppState state)
+        {
+            this.state = state;
+            this.stateTimestamps.put(state,Long.valueOf(System.currentTimeMillis()));
+        }
+
+        public int getVersion()
+        {
+            return version;
+        }
+
+        public App getApp()
+        {
+            return app;
+        }
+
+        public AppState getState()
+        {
+            return state;
+        }
+
+        public Map<AppState, Long> getStateTimestamps()
+        {
+            return stateTimestamps;
+        }
+    }
+
+    static class BoundMethod
+    {
+        Object obj;
+        AppPhase lifecycle;
+        Method method;
+    }
+
+    private Map<String, AppEntry> appmap = new HashMap<String, AppEntry>();
+    private Map<AppPhase, List<BoundMethod>> lifecyclebindings = new HashMap<AppPhase, List<BoundMethod>>();
+
+    public void addAppProvider(AppProvider provider)
+    {
+        // TODO Auto-generated method stub
+    }
+
+    public void addLifecycleBinding(Object obj)
+    {
+        for (Method method : obj.getClass().getDeclaredMethods())
+        {
+            // Does it have annotation?
+            if (!method.isAnnotationPresent(DeployLifecycleBinding.class))
+            {
+                continue; // skip
+            }
+
+            // Is it public?
+            if (!Modifier.isPublic(method.getModifiers()))
+            {
+                continue; // skip
+            }
+
+            // Does it return void?
+            if (method.getReturnType() != Void.TYPE)
+            {
+                continue; // skip
+            }
+
+            // Does it have specified parameters?
+            Class<?> params[] = method.getParameterTypes();
+            if (params == null)
+            {
+                continue; // skip
+            }
+            if (params.length != 2)
+            {
+                continue; // skip
+            }
+
+            if (params[0].isAssignableFrom(AppPhase.class) && params[1].isAssignableFrom(App.class))
+            {
+                DeployLifecycleBinding bindingAnno = method.getAnnotation(DeployLifecycleBinding.class);
+
+                for (AppPhase lifecycle : bindingAnno.phases())
+                {
+                    // Create Method -> Object binding reference.
+                    BoundMethod bound = new BoundMethod();
+                    bound.obj = obj;
+                    bound.method = method;
+                    bound.lifecycle = lifecycle;
+
+                    // Assign to map
+                    List<BoundMethod> bindings = lifecyclebindings.get(lifecycle);
+                    if (bindings == null)
+                    {
+                        bindings = new ArrayList<BoundMethod>();
+                    }
+                    bindings.add(bound);
+
+                    lifecyclebindings.put(lifecycle,bindings);
+                }
+            }
+        }
+    }
+
+    public App getApp(String appId)
+    {
+        AppEntry entry = appmap.get(appId);
+        if (entry == null)
+        {
+            return null;
+        }
+        return entry.app;
+    }
+
+    public AppEntry getAppEntry(String appId)
+    {
+        return appmap.get(appId);
+    }
+
+    public Collection<AppProvider> getAppProviders()
+    {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public Collection<App> getApps()
+    {
+        List<App> apps = new ArrayList<App>();
+        for (AppEntry entry : appmap.values())
+        {
+            apps.add(entry.app);
+        }
+        return apps;
+    }
+
+    public Collection<AppEntry> getAppEntries()
+    {
+        return appmap.values();
+    }
+
+    /**
+     * Get Set of {@link App}s by {@link AppState}
+     * 
+     * @param state
+     *            the state to look for.
+     * @return
+     */
+    public Collection<App> getApps(AppState state)
+    {
+        List<App> apps = new ArrayList<App>();
+        for (AppEntry entry : appmap.values())
+        {
+            if (entry.state == state)
+            {
+                apps.add(entry.app);
+            }
+        }
+        return apps;
+    }
+
+    /**
+     * Get all {@link AppPhase} bound objects.
+     * 
+     * @return Set of Object(s) for all lifecycle bindings. never null.
+     */
+    public Set<Object> getLifecycleBindings()
+    {
+        Set<Object> boundset = new HashSet<Object>();
+
+        for (List<BoundMethod> bindings : lifecyclebindings.values())
+        {
+            for (BoundMethod bound : bindings)
+            {
+                boundset.add(bound.obj);
+            }
+        }
+
+        return boundset;
+    }
+
+    /**
+     * Get all objects bound to a specific {@link AppPhase}
+     * 
+     * @return Set of Object(s) for specific lifecycle bindings. never null.
+     */
+    public Set<Object> getLifecycleBindings(AppPhase phase)
+    {
+        Set<Object> boundset = new HashSet<Object>();
+
+        List<BoundMethod> bindings = lifecyclebindings.get(phase);
+        if (bindings == null)
+        {
+            return boundset;
+        }
+
+        for (BoundMethod bound : bindings)
+        {
+            boundset.add(bound.obj);
+        }
+
+        return boundset;
+    }
+
+    /**
+     * Receive an app for processing.
+     * 
+     * Usually called by the various {@link AppProvider} implementations.
+     */
+    public void receiveApp(App app)
+    {
+        AppEntry entry = appmap.get(app.getId());
+        if (entry == null)
+        {
+            // New Entry
+            entry = new AppEntry();
+            entry.version = 0;
+            entry.updateState(AppState.UNAVAILABLE);
+        }
+        else
+        {
+            // Existing Entry
+            entry.version++;
+
+            // TODO: determine state change
+            // TODO: determine redeploy need?
+        }
+
+        // TODO: Should immediately attempt to start?
+        // TODO: Allow bound lifecycle to interrupt deploy (ie: StageApprovalPreStart binding?)
+
+        entry.app = app;
+        appmap.put(app.getId(),entry);
+    }
+
+    public void removeAppProvider(AppProvider provider)
+    {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * Move an {@link App} through the {@link AppPhase} to the desired {@link AppState}, executing each lifecycle step
+     * in the process to reach the desired state.
+     * 
+     * @param app
+     * @param desiredState
+     */
+    public void requestAppGoal(String appId, AppState desiredState)
+    {
+        AppEntry appentry = getAppEntry(appId);
+
+        if (appentry.state == desiredState)
+        {
+            // nothing to do. already there.
+            return;
+        }
+
+        // Compute lifecycle steps
+
+    }
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/annotations/DeployLifecycleBinding.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/annotations/DeployLifecycleBinding.java
new file mode 100644
index 0000000..872d16d
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/annotations/DeployLifecycleBinding.java
@@ -0,0 +1,32 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.eclipse.jetty.deploy.AppPhase;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@Documented
+public @interface DeployLifecycleBinding
+{
+    AppPhase[] phases();
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/bindings/DefaultDeployer.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/bindings/DefaultDeployer.java
new file mode 100644
index 0000000..d5c81d1
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/bindings/DefaultDeployer.java
@@ -0,0 +1,29 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy.bindings;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppPhase;
+import org.eclipse.jetty.deploy.annotations.DeployLifecycleBinding;
+
+public class DefaultDeployer
+{
+    @DeployLifecycleBinding(phases = AppPhase.DEPLOY)
+    public void handleAppDeploy(AppPhase lifecycle, App app)
+    {
+        System.out.println("Default Deploy: " + app);
+    }
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/ContextsDirAppProvider.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/ContextsDirAppProvider.java
new file mode 100644
index 0000000..ff31fbb
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/ContextsDirAppProvider.java
@@ -0,0 +1,41 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy.providers;
+
+import org.eclipse.jetty.deploy.AppProvider;
+import org.eclipse.jetty.deploy.AppReceiver;
+
+/**
+ * Backwards Compatible AppProvider for Monitoring a Contexts directory and deploying All Contexts.
+ * 
+ * Similar in scope to the original org.eclipse.jetty.deploy.ContextDeployer
+ */
+public class ContextsDirAppProvider implements AppProvider
+{
+
+    public void startProvider(AppReceiver receiver)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void stopProvider()
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/WebappsDirAppProvider.java b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/WebappsDirAppProvider.java
new file mode 100644
index 0000000..b67e849
--- /dev/null
+++ b/jetty-deploy-manager/src/main/java/org/eclipse/jetty/deploy/providers/WebappsDirAppProvider.java
@@ -0,0 +1,41 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy.providers;
+
+import org.eclipse.jetty.deploy.AppProvider;
+import org.eclipse.jetty.deploy.AppReceiver;
+
+/**
+ * Backwards Compatible AppProvider for Monitoring a Webapps directory and deploying All apps.
+ * 
+ * Similar in scope to the original org.eclipse.jetty.deploy.WebAppDeployer
+ */
+public class WebappsDirAppProvider implements AppProvider
+{
+
+    public void startProvider(AppReceiver receiver)
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+    public void stopProvider()
+    {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png b/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png
new file mode 100644
index 0000000..071c747
--- /dev/null
+++ b/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png
Binary files differ
diff --git a/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg b/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg
new file mode 100644
index 0000000..dc6974c
--- /dev/null
+++ b/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg
@@ -0,0 +1,482 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Graphviz version 2.20.2 (Mon Mar 30 10:11:53 UTC 2009)
+     For user: (joakim) Joakim Erdfelt,,, -->
+<!-- Title: G Pages: 1 -->
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="765"
+   height="900"
+   viewBox="0 0 327 590"
+   id="svg2"
+   sodipodi:version="0.32"
+   inkscape:version="0.46"
+   sodipodi:docname="AppLifecycle.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   version="1.0"
+   inkscape:export-filename="/home/joakim/code/webtide/jetty7-git/sandbox/jetty-deploy-manager/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png"
+   inkscape:export-xdpi="40"
+   inkscape:export-ydpi="40">
+  <metadata
+     id="metadata184">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs182">
+    <marker
+       inkscape:stockid="Arrow2Lstart"
+       orient="auto"
+       refY="0.0"
+       refX="0.0"
+       id="Arrow2Lstart"
+       style="overflow:visible">
+      <path
+         id="path4328"
+         style="font-size:12.0;fill-rule:evenodd;stroke-width:0.62500000;stroke-linejoin:round"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
+         transform="scale(1.1) translate(1,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow2Lend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow2Lend"
+       style="overflow:visible">
+      <path
+         id="path3605"
+         style="font-size:12px;fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+         d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.97309,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z"
+         transform="matrix(-1.1,0,0,-1.1,-1.1,0)" />
+    </marker>
+    <marker
+       inkscape:stockid="TriangleOutL"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="TriangleOutL"
+       style="overflow:visible">
+      <path
+         id="path3727"
+         d="M 5.77,0 L -2.88,5 L -2.88,-5 L 5.77,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+         transform="scale(0.8,0.8)" />
+    </marker>
+    <marker
+       inkscape:stockid="Arrow1Lend"
+       orient="auto"
+       refY="0"
+       refX="0"
+       id="Arrow1Lend"
+       style="overflow:visible">
+      <path
+         id="path3587"
+         d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z"
+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none"
+         transform="matrix(-0.8,0,0,-0.8,-10,0)" />
+    </marker>
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 368.75 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="408.75 : 368.75 : 1"
+       inkscape:persp3d-origin="204.375 : 245.83333 : 1"
+       id="perspective186" />
+    <title
+       id="title5">G</title>
+    <title
+       id="title10">incoming_app</title>
+    <title
+       id="title17">command_deploy</title>
+    <title
+       id="title33">state_deployed</title>
+  </defs>
+  <sodipodi:namedview
+     inkscape:window-height="1151"
+     inkscape:window-width="1920"
+     inkscape:pageshadow="2"
+     inkscape:pageopacity="1"
+     guidetolerance="10"
+     gridtolerance="10.0"
+     objecttolerance="13"
+     borderopacity="1.0"
+     bordercolor="#666666"
+     pagecolor="#ffffff"
+     id="base"
+     showgrid="false"
+     inkscape:zoom="0.92828283"
+     inkscape:cx="387.34766"
+     inkscape:cy="428.51283"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:current-layer="svg2"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:document-units="cm"
+     units="in"
+     inkscape:snap-guide="true"
+     inkscape:snap-bbox="true"
+     inkscape:bbox-paths="false"
+     inkscape:bbox-nodes="false"
+     inkscape:snap-intersection-line-segments="true"
+     inkscape:object-paths="true"
+     inkscape:object-nodes="true"
+     inkscape:snap-center="false">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3514"
+       dotted="true"
+       visible="true"
+       enabled="true"
+       units="cm"
+       spacingx="0.25cm"
+       spacingy="0.25cm"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <g
+     id="g3445"
+     transform="matrix(1.2491264,0,0,1.2491264,109.41031,84.061397)">
+    <polygon
+       style="fill:#ffa500;stroke:#ffa500;stroke-width:0.80055952"
+       points="228,-510 144,-510 144,-468 228,-468 228,-510 "
+       id="polygon3447"
+       transform="translate(4,586)" />
+    <text
+       x="190"
+       y="92.600006"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text3449"
+       sodipodi:linespacing="125%">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3451"
+         x="190"
+         y="92.600006">Phase</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan3453"
+         x="190"
+         y="110.60001">DEPLOY</tspan>
+    </text>
+  </g>
+  <g
+     id="g3917">
+    <polygon
+       transform="matrix(1.6111428,0,0,1.2491264,-118.06476,648.90074)"
+       id="polygon3491"
+       points="230,-432 138,-432 138,-390 230,-390 230,-432 "
+       style="fill:#00ffff;stroke:#00ffff;stroke-width:0.70490372" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3493"
+       style="font-size:17.98741913px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       y="130.01364"
+       x="178.38551">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3503"
+         x="178.38551"
+         y="130.01364">State</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan3505"
+         x="178.38551"
+         y="152.49792">UNAVAILABLE</tspan>
+    </text>
+  </g>
+  <g
+     id="g3549"
+     transform="matrix(1.2491264,0,0,1.2491264,-227.0731,84.061397)">
+    <polygon
+       style="fill:#ffa500;stroke:#ffa500;stroke-width:0.80055952"
+       points="228,-510 144,-510 144,-468 228,-468 228,-510 "
+       id="polygon3551"
+       transform="translate(4,586)" />
+    <text
+       x="190"
+       y="92.600006"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text3553"
+       sodipodi:linespacing="125%">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3559"
+         x="190"
+         y="92.600006">Phase</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan3561"
+         x="190"
+         y="110.60001">UNDEPLOY</tspan>
+    </text>
+  </g>
+  <g
+     id="g3483"
+     transform="matrix(1.2491264,0,0,1.2491264,-102.88457,7.5836656)">
+    <ellipse
+       style="fill:#c1c1c1;fill-opacity:1;stroke:none"
+       cx="222"
+       cy="-564"
+       rx="96.784599"
+       ry="18"
+       id="ellipse3485"
+       sodipodi:cx="222"
+       sodipodi:cy="-564"
+       sodipodi:rx="96.784599"
+       sodipodi:ry="18"
+       transform="translate(4,586)" />
+    <text
+       x="226"
+       y="26.099976"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text3487"
+       sodipodi:linespacing="125%">Incoming WebApp</text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.9988189;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none;marker-end:url(#Arrow2Lend)"
+     d="M 180.40071,57.523395 L 180.40071,108.36997"
+     id="path2594"
+     sodipodi:nodetypes="cc" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3923"
+     d="M 252.49807,154.27312 L 294.28101,190.29424"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 294.28101,213.09705 L 236.46989,265.1469"
+     id="path3925"
+     sodipodi:nodetypes="cc" />
+  <g
+     id="g3469"
+     transform="matrix(1.2491264,0,0,1.2491264,-55.825685,55.721845)">
+    <polygon
+       style="fill:#00ffff;stroke:#00ffff;stroke-width:0.80055952"
+       points="230,-432 138,-432 138,-390 230,-390 230,-432 "
+       id="polygon3471"
+       transform="translate(4,586)" />
+    <text
+       x="188"
+       y="170.60001"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text3473"
+       sodipodi:linespacing="125%">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3475"
+         x="188"
+         y="170.60001">State</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan3477"
+         x="188"
+         y="188.60001">DEPLOYED</tspan>
+    </text>
+  </g>
+  <g
+     id="g5403"
+     transform="matrix(1.2491264,0,0,1.2491264,-56.840602,191.55677)">
+    <polygon
+       style="fill:#00ffff;stroke:#00ffff;stroke-width:0.80055952"
+       points="230,-432 138,-432 138,-390 230,-390 230,-432 "
+       id="polygon5405"
+       transform="translate(4,586)" />
+    <text
+       x="188"
+       y="170.60001"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text5407"
+       sodipodi:linespacing="125%">
+      <tspan
+         sodipodi:role="line"
+         id="tspan5417"
+         x="188"
+         y="170.60001">State</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan5419"
+         x="188"
+         y="188.60001">STAGED</tspan>
+    </text>
+  </g>
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3929"
+     d="M 120.53534,398.20562 L 62.724228,352.50272"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3935"
+     d="M 294.28101,355.83348 L 235.45498,398.20563"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <g
+     transform="matrix(1.2491264,0,0,1.2491264,109.41031,219.89633)"
+     id="g3535">
+    <polygon
+       transform="translate(4,586)"
+       id="polygon3537"
+       points="228,-510 144,-510 144,-468 228,-468 228,-510 "
+       style="fill:#ffa500;stroke:#ffa500;stroke-width:0.80055952" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text3539"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       y="92.600006"
+       x="190">
+      <tspan
+         sodipodi:role="line"
+         id="tspan3545"
+         x="190"
+         y="92.600006">Phase</tspan>
+      <tspan
+         sodipodi:role="line"
+         id="tspan3547"
+         x="190"
+         y="110.60001">STAGE</tspan>
+    </text>
+  </g>
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3927"
+     d="M 236.46989,284.21436 L 293.65647,330.6791"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3941"
+     d="M 62.724228,186.76323 L 104.27295,153.52683"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <path
+     sodipodi:nodetypes="cc"
+     id="path3943"
+     d="M 121.55027,263.8367 L 62.724228,212.94524"
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+  <text
+     x="-10.835248"
+     y="23.769073"
+     style="font-size:20.46376419px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold Italic"
+     id="text2456"
+     sodipodi:linespacing="100%">
+    <tspan
+       sodipodi:role="line"
+       id="tspan2460"
+       x="-10.835244"
+       y="23.769073"
+       style="font-size:20.46376419px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold Italic">Deployment</tspan>
+    <tspan
+       sodipodi:role="line"
+       x="-10.835247"
+       y="44.232838"
+       id="tspan2464"
+       style="font-size:20.46376419px;font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:center;line-height:100%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans Bold Italic">Lifecycle</tspan>
+  </text>
+  <g
+     id="g2472"
+     transform="matrix(1.2491264,0,0,1.2491264,109.41031,355.73126)">
+    <polygon
+       style="fill:#ffa500;stroke:#ffa500;stroke-width:0.80055952"
+       points="228,-510 144,-510 144,-468 228,-468 228,-510 "
+       id="polygon2474"
+       transform="translate(4,586)" />
+    <text
+       x="190"
+       y="92.600006"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text2476"
+       sodipodi:linespacing="125%">
+      <tspan
+         y="92.600006"
+         x="190"
+         id="tspan2478"
+         sodipodi:role="line">Phase</tspan>
+      <tspan
+         y="110.60001"
+         x="190"
+         id="tspan2480"
+         sodipodi:role="line">START</tspan>
+    </text>
+  </g>
+  <g
+     id="g2482"
+     transform="matrix(1.2491264,0,0,1.2491264,-227.0731,219.89633)">
+    <polygon
+       style="fill:#ffa500;stroke:#ffa500;stroke-width:0.80055952"
+       points="228,-510 144,-510 144,-468 228,-468 228,-510 "
+       id="polygon2484"
+       transform="translate(4,586)" />
+    <text
+       x="190"
+       y="92.600006"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       id="text2486"
+       sodipodi:linespacing="125%">
+      <tspan
+         y="92.600006"
+         x="190"
+         id="tspan2488"
+         sodipodi:role="line">Phase</tspan>
+      <tspan
+         y="110.60001"
+         x="190"
+         id="tspan2490"
+         sodipodi:role="line">STOP</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 236.46989,420.04929 L 293.65647,466.51403"
+     id="path2492"
+     sodipodi:nodetypes="cc" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 62.724228,326.37326 L 121.55027,286.42657"
+     id="path2494"
+     sodipodi:nodetypes="cc" />
+  <g
+     transform="matrix(1.2491264,0,0,1.2491264,-56.840602,327.3917)"
+     id="g2551">
+    <polygon
+       transform="translate(4,586)"
+       id="polygon2553"
+       points="230,-432 138,-432 138,-390 230,-390 230,-432 "
+       style="fill:#00ffff;stroke:#00ffff;stroke-width:0.80055952" />
+    <text
+       sodipodi:linespacing="125%"
+       id="text2555"
+       style="font-size:14.39999962px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;font-family:Sans;-inkscape-font-specification:Sans"
+       y="170.60001"
+       x="188">
+      <tspan
+         y="170.60001"
+         x="188"
+         id="tspan2557"
+         sodipodi:role="line">State</tspan>
+      <tspan
+         y="188.60001"
+         x="188"
+         id="tspan2559"
+         sodipodi:role="line">STARTED</tspan>
+    </text>
+  </g>
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 120.53534,536.86536 L 23.883096,367.29325"
+     id="path2561"
+     sodipodi:nodetypes="cc" />
+  <path
+     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.99881887;stroke-linecap:butt;stroke-linejoin:miter;marker-end:url(#Arrow2Lend);stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     d="M 294.28101,491.66841 L 235.45498,545.3398"
+     id="path2563"
+     sodipodi:nodetypes="cc" />
+</svg>
diff --git a/jetty-deploy-manager/src/main/javadoc/overview.html b/jetty-deploy-manager/src/main/javadoc/overview.html
new file mode 100644
index 0000000..689d4f4
--- /dev/null
+++ b/jetty-deploy-manager/src/main/javadoc/overview.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<HTML>
+  <HEAD>
+    <TITLE>Jetty DeploymentManager API Overview</TITLE>
+  </HEAD>
+  <BODY>
+    Short overview of the API.
+  </BODY>
+</HTML>
diff --git a/jetty-deploy-manager/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt b/jetty-deploy-manager/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt
new file mode 100644
index 0000000..ed00104
--- /dev/null
+++ b/jetty-deploy-manager/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt
@@ -0,0 +1 @@
+# Default Bindings
\ No newline at end of file
diff --git a/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/AppLifecycleTest.java b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/AppLifecycleTest.java
new file mode 100644
index 0000000..3353a02
--- /dev/null
+++ b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/AppLifecycleTest.java
@@ -0,0 +1,196 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Just an overly picky test case to validate the potential paths.
+ */
+public class AppLifecycleTest
+{
+    private void addExpected(List<AppPhase> expected, AppPhase phase)
+    {
+        expected.add(AppPhase.valueOf("PRE_" + phase.name()));
+        expected.add(phase);
+        expected.add(AppPhase.valueOf("POST_" + phase.name()));
+    }
+
+    private void assertNoPhases(AppState from, AppState to)
+    {
+        assertPhases(from,to,new ArrayList<AppPhase>());
+    }
+
+    private void assertPhases(AppState from, AppState to, List<AppPhase> expected)
+    {
+        AppLifecycle lifecycle = new AppLifecycle();
+        List<AppPhase> actual = lifecycle.getPhases(from,to);
+        String msg = "Lifecycle.phases from " + from + " to " + to;
+        Assert.assertNotNull(msg + " should never be null",actual);
+
+        if (expected.size() != actual.size())
+        {
+            System.out.println("/* Expected Phases */");
+            for (AppPhase phase : expected)
+            {
+                System.out.println(phase);
+            }
+            System.out.println("/* Actual Phases */");
+            for (AppPhase phase : actual)
+            {
+                System.out.println(phase);
+            }
+
+            Assert.assertEquals(msg + " / count",0,actual.size());
+        }
+
+        for (int i = 0, n = expected.size(); i < n; i++)
+        {
+            Assert.assertEquals(msg + "[" + i + "]",expected.get(i),actual.get(i));
+        }
+    }
+
+    @Test
+    public void testGetPhases_Deployed_Deployed()
+    {
+        assertNoPhases(AppState.DEPLOYED,AppState.DEPLOYED);
+    }
+
+    @Test
+    public void testGetPhases_Deployed_Staged()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STAGE);
+        assertPhases(AppState.DEPLOYED,AppState.STAGED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Deployed_Started()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STAGE);
+        addExpected(expected,AppPhase.START);
+        assertPhases(AppState.DEPLOYED,AppState.STARTED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Deployed_Unavailable()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.UNDEPLOY);
+        assertPhases(AppState.DEPLOYED,AppState.UNAVAILABLE,expected);
+    }
+
+    @Test
+    public void testGetPhases_Staged_Deployed()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STOP);
+        assertPhases(AppState.STAGED,AppState.DEPLOYED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Staged_Staged()
+    {
+        assertNoPhases(AppState.STAGED,AppState.STAGED);
+    }
+
+    @Test
+    public void testGetPhases_Staged_Started()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.START);
+        assertPhases(AppState.STAGED,AppState.STARTED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Staged_Unavailable()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STOP);
+        addExpected(expected,AppPhase.UNDEPLOY);
+        assertPhases(AppState.STAGED,AppState.UNAVAILABLE,expected);
+    }
+
+    @Test
+    public void testGetPhases_Started_Deployed()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STOP);
+        assertPhases(AppState.STARTED,AppState.DEPLOYED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Started_Staged()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STOP);
+        addExpected(expected,AppPhase.STAGE);
+        assertPhases(AppState.STARTED,AppState.STAGED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Started_Started()
+    {
+        assertNoPhases(AppState.STARTED,AppState.STARTED);
+    }
+
+    @Test
+    public void testGetPhases_Started_Unvailable()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.STOP);
+        addExpected(expected,AppPhase.UNDEPLOY);
+        assertPhases(AppState.STARTED,AppState.UNAVAILABLE,expected);
+    }
+
+    @Test
+    public void testGetPhases_Unavailable_Deployed()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.DEPLOY);
+        assertPhases(AppState.UNAVAILABLE,AppState.DEPLOYED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Unavailable_Staged()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.DEPLOY);
+        addExpected(expected,AppPhase.STAGE);
+        assertPhases(AppState.UNAVAILABLE,AppState.STAGED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Unavailable_Started()
+    {
+        List<AppPhase> expected = new ArrayList<AppPhase>();
+        addExpected(expected,AppPhase.DEPLOY);
+        addExpected(expected,AppPhase.STAGE);
+        addExpected(expected,AppPhase.START);
+        assertPhases(AppState.UNAVAILABLE,AppState.STARTED,expected);
+    }
+
+    @Test
+    public void testGetPhases_Unavailable_Uavailable()
+    {
+        assertNoPhases(AppState.UNAVAILABLE,AppState.UNAVAILABLE);
+    }
+}
diff --git a/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerStateTransitionTest.java b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerStateTransitionTest.java
new file mode 100644
index 0000000..fb2462c
--- /dev/null
+++ b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerStateTransitionTest.java
@@ -0,0 +1,84 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DeploymentManagerStateTransitionTest
+{
+    @Test
+    public void testBinding()
+    {
+        LifecycleOrderTracker phasetracker = new LifecycleOrderTracker();
+        DeploymentManager depman = new DeploymentManager();
+        depman.addLifecycleBinding(phasetracker);
+
+        Set<Object> allbindings = depman.getLifecycleBindings();
+        Assert.assertNotNull("All Bindings should never be null",allbindings);
+        Assert.assertEquals("All Bindings.size",1,allbindings.size());
+
+        Set<Object> deploybindings = depman.getLifecycleBindings(AppPhase.DEPLOY);
+        Assert.assertNotNull("DEPLOY Bindings should never be null",deploybindings);
+        Assert.assertEquals("DEPLOY Bindings.size",1,deploybindings.size());
+    }
+
+    @Test
+    public void testStateTransition_NewToDeployed()
+    {
+        LifecycleOrderTracker phasetracker = new LifecycleOrderTracker();
+        DeploymentManager depman = new DeploymentManager();
+        depman.addLifecycleBinding(phasetracker);
+        App app = new TestApp();
+
+        // Pretend to AppProvider ...
+        depman.receiveApp(app);
+
+        // Request Deploy of App
+        depman.requestAppGoal(app.getId(),AppState.DEPLOYED);
+
+        // Setup Expectations.
+        List<AppPhase> expectedOrder = new ArrayList<AppPhase>();
+        expectedOrder.add(AppPhase.PRE_DEPLOY);
+        expectedOrder.add(AppPhase.DEPLOY);
+        expectedOrder.add(AppPhase.POST_DEPLOY);
+
+        phasetracker.assertExpected("Test StateTransition / New -> Deployed",expectedOrder);
+    }
+
+    @Test
+    public void testStateTransition_Receive()
+    {
+        LifecycleOrderTracker phasetracker = new LifecycleOrderTracker();
+        DeploymentManager depman = new DeploymentManager();
+        depman.addLifecycleBinding(phasetracker);
+        App app = new TestApp();
+
+        // Pretend to AppProvider
+        depman.receiveApp(app);
+
+        // Perform no goal request.
+
+        // Setup Expectations.
+        List<AppPhase> expectedOrder = new ArrayList<AppPhase>();
+
+        phasetracker.assertExpected("Test StateTransition / New only",expectedOrder);
+    }
+}
diff --git a/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
new file mode 100644
index 0000000..6818bdb
--- /dev/null
+++ b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
@@ -0,0 +1,31 @@
+package org.eclipse.jetty.deploy;
+
+import java.util.Collection;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DeploymentManagerTest
+{
+    @Test
+    public void testReceiveApp()
+    {
+        LifecycleOrderTracker phasetracker = new LifecycleOrderTracker();
+        DeploymentManager depman = new DeploymentManager();
+        depman.addLifecycleBinding(phasetracker);
+        App app = new TestApp();
+
+        // Pretend to AppProvider ...
+        depman.receiveApp(app);
+
+        // Test app tracking
+        Collection<App> apps = depman.getApps();
+        Assert.assertNotNull("Should never be null",apps);
+        Assert.assertEquals("Expected App Count",1,apps.size());
+
+        // Test app get
+        App actual = depman.getApp("test-app");
+        Assert.assertNotNull("Should have gotten app (by id)",actual);
+        Assert.assertEquals("Should have gotten app (by id)",actual);
+    }
+}
diff --git a/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/LifecycleOrderTracker.java b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/LifecycleOrderTracker.java
new file mode 100644
index 0000000..4b11759
--- /dev/null
+++ b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/LifecycleOrderTracker.java
@@ -0,0 +1,54 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.deploy.annotations.DeployLifecycleBinding;
+
+/**
+ * Binds to all lifecycle phases, and tracks the order of the lifecycle phases for testing purposes.
+ */
+public class LifecycleOrderTracker
+{
+    List<AppPhase> phaseOrder = new ArrayList<AppPhase>();
+
+    public void clear()
+    {
+        phaseOrder.clear();
+    }
+
+    public List<AppPhase> getPhaseOrder()
+    {
+        return phaseOrder;
+    }
+
+    @DeployLifecycleBinding(phases =
+    { AppPhase.PRE_DEPLOY, AppPhase.DEPLOY, AppPhase.POST_DEPLOY, AppPhase.PRE_STAGE, AppPhase.STAGE, AppPhase.POST_STAGE,
+            AppPhase.PRE_START, AppPhase.START, AppPhase.POST_START, AppPhase.PRE_STOP, AppPhase.STOP, AppPhase.POST_STOP,
+            AppPhase.PRE_UNDEPLOY, AppPhase.UNDEPLOY, AppPhase.POST_UNDEPLOY })
+    public void handleAppDeploy(AppPhase lifecycle, App app)
+    {
+        phaseOrder.add(lifecycle);
+    }
+
+    public void assertExpected(String msg, List<AppPhase> expectedOrder)
+    {
+        // TODO Auto-generated method stub
+
+    }
+}
diff --git a/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/TestApp.java b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/TestApp.java
new file mode 100644
index 0000000..793522a
--- /dev/null
+++ b/jetty-deploy-manager/src/test/java/org/eclipse/jetty/deploy/TestApp.java
@@ -0,0 +1,53 @@
+// ========================================================================
+// Copyright (c) Webtide LLC
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at 
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.apache.org/licenses/LICENSE-2.0.txt
+//
+// You may elect to redistribute this code under either of these licenses. 
+// ========================================================================
+package org.eclipse.jetty.deploy;
+
+import java.io.File;
+
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+public class TestApp implements App
+{
+    public File getArchivePath()
+    {
+        return null;
+    }
+
+    public ContextHandler getDesiredContextHandler()
+    {
+        return null;
+    }
+
+    public String getId()
+    {
+        return "test-app";
+    }
+
+    public String getName()
+    {
+        return "Test App";
+    }
+
+    public String getOrigin()
+    {
+        return null;
+    }
+
+    public File getWorkDir()
+    {
+        return null;
+    }
+}