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;
+ }
+}