diff --git a/jetty-deploy/pom.xml b/jetty-deploy/pom.xml
index 49e51e88c52..f0f9b096d5f 100644
--- a/jetty-deploy/pom.xml
+++ b/jetty-deploy/pom.xml
@@ -30,25 +30,25 @@
org.apache.maven.pluginsmaven-jar-plugin
-
+ ${project.build.outputDirectory}/META-INF/MANIFEST.MF
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- attach-sources
-
- jar
-
-
-
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ attach-sources
+
+ jar
+
+
+
-
+
@@ -61,10 +61,31 @@
jetty-xml${project.version}
+
+ org.eclipse.jetty
+ jetty-webapp-verifier
+ ${project.version}
+ true
+ junitjunit
+ 4.7test
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 2.5
+
+ true
+ true
+ true
+
+
+
+
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
new file mode 100644
index 00000000000..6ef27bd7c66
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/App.java
@@ -0,0 +1,220 @@
+// ========================================================================
+// 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 java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jetty.deploy.util.FileID;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.xml.sax.SAXException;
+
+/**
+ * The information about an App that is managed by the {@link DeploymentManager}
+ */
+public class App
+{
+ private File archivePath;
+ private ContextHandler handler;
+ private boolean extractWars = false;
+ private boolean parentLoaderPriority = false;
+ private String defaultsDescriptor;
+ private String originId;
+
+ /**
+ * Create an App with specified Origin ID and archivePath
+ *
+ * @param originId
+ * the origin ID (The ID that the {@link AppProvider} knows about)
+ * @param archivePath
+ * the path to the app (can be a file *.war or *.jar, or a webapp exploded directory)
+ * @see App#getOriginId()
+ * @see App#getContextId()
+ */
+ public App(String originId, File archivePath)
+ {
+ this.originId = originId;
+ this.archivePath = archivePath;
+ }
+
+ /**
+ * 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
+ */
+ public File getArchivePath()
+ {
+ return this.archivePath;
+ }
+
+ /**
+ * Get ContextHandler for the App.
+ *
+ * Create it if needed.
+ *
+ * @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}
+ * @throws Exception
+ */
+ public ContextHandler getContextHandler(DeploymentManager deployMgr) throws Exception
+ {
+ if (handler == null)
+ {
+ if (FileID.isXmlFile(archivePath))
+ {
+ this.handler = createContextFromXml(deployMgr);
+ }
+ else if (FileID.isWebArchive(archivePath))
+ {
+ // Treat as a Web Archive.
+ this.handler = createContextDefault(deployMgr);
+ }
+ else
+ {
+ throw new IllegalStateException("Not an XML or Web Archive: " + archivePath.getAbsolutePath());
+ }
+
+ this.handler.setAttributes(new AttributesMap(deployMgr.getContextAttributes()));
+ }
+ return handler;
+ }
+
+ private ContextHandler createContextDefault(DeploymentManager deploymgr)
+ {
+ String context = archivePath.getName();
+
+ // Context Path is the same as the archive.
+ if (FileID.isWebArchiveFile(archivePath))
+ {
+ context = context.substring(0,context.length() - 4);
+ }
+
+ // Context path is "/" in special case of archive (or dir) named "root"
+ if (context.equalsIgnoreCase("root") || context.equalsIgnoreCase("root/"))
+ {
+ context = URIUtil.SLASH;
+ }
+
+ // Ensure "/" is Prepended to all context paths.
+ if (context.charAt(0) != '/')
+ {
+ context = "/" + context;
+ }
+
+ // Ensure "/" is Not Trailing in context paths.
+ if (context.endsWith("/") && context.length() > 0)
+ {
+ context = context.substring(0,context.length() - 1);
+ }
+
+ WebAppContext wah = new WebAppContext();
+ wah.setContextPath(context);
+ wah.setWar(archivePath.getAbsolutePath());
+ if (defaultsDescriptor != null)
+ {
+ wah.setDefaultsDescriptor(defaultsDescriptor);
+ }
+ wah.setExtractWAR(extractWars);
+ wah.setParentLoaderPriority(parentLoaderPriority);
+
+ return wah;
+ }
+
+ @SuppressWarnings("unchecked")
+ private ContextHandler createContextFromXml(DeploymentManager deploymgr) throws MalformedURLException, IOException, SAXException, Exception
+ {
+ Resource resource = Resource.newResource(this.archivePath.toURI());
+ if (!resource.exists())
+ {
+ return null;
+ }
+
+ XmlConfiguration xmlc = new XmlConfiguration(resource.getURL());
+ Map props = new HashMap();
+ props.put("Server",deploymgr.getServer());
+ if (deploymgr.getConfigurationManager() != null)
+ {
+ props.putAll(deploymgr.getConfigurationManager().getProperties());
+ }
+
+ xmlc.setProperties(props);
+ return (ContextHandler)xmlc.configure();
+ }
+
+ public boolean isExtractWars()
+ {
+ return extractWars;
+ }
+
+ public void setExtractWars(boolean extractWars)
+ {
+ this.extractWars = extractWars;
+ }
+
+ public boolean isParentLoaderPriority()
+ {
+ return parentLoaderPriority;
+ }
+
+ public void setParentLoaderPriority(boolean parentLoaderPriority)
+ {
+ this.parentLoaderPriority = parentLoaderPriority;
+ }
+
+ public String getDefaultsDescriptor()
+ {
+ return defaultsDescriptor;
+ }
+
+ public void setDefaultsDescriptor(String defaultsDescriptor)
+ {
+ this.defaultsDescriptor = defaultsDescriptor;
+ }
+
+ /**
+ * The unique id of the {@link App} relating to how it is installed on the jetty server side.
+ *
+ * @return the generated Id for the App.
+ */
+ public String getContextId()
+ {
+ if (this.handler == null)
+ {
+ return null;
+ }
+ return this.handler.getContextPath();
+ }
+
+ /**
+ * The origin of this {@link App} as specified by the {@link AppProvider}
+ *
+ * @return String representing the origin of this app.
+ */
+ public String getOriginId()
+ {
+ return this.originId;
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java
new file mode 100644
index 00000000000..49c3deaf4e2
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppLifeCycle.java
@@ -0,0 +1,192 @@
+// ========================================================================
+// 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.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.jetty.deploy.graph.Graph;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * The lifecycle of an App in the {@link DeploymentManager}.
+ *
+ * Setups a the default {@link Graph}, and manages the bindings to the life cycle via the {@link DeployLifeCycleBinding}
+ * annotation.
+ *
+ *
+ */
+public class AppLifeCycle extends Graph
+{
+ public static interface Binding
+ {
+ /**
+ * Get a list of targets that this implementation should bind to.
+ *
+ * @return the array of String node names to bind to. (use "*" to bind to all known node names)
+ */
+ String[] getBindingTargets();
+
+ /**
+ * Event called to process a {@link AppLifeCycle} binding.
+ *
+ * @param node
+ * the node being processed
+ * @param app
+ * the app being processed
+ * @param deploymentManager
+ * the {@link DeploymentManager} tracking the {@link AppLifeCycle} and {@link App}
+ * @throws Exception
+ * if any problem severe enough to halt the AppLifeCycle processing
+ */
+ void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception;
+ }
+
+ // Private string constants defined to avoid typos on repeatedly used strings
+ private static final String NODE_UNDEPLOYED = "undeployed";
+ private static final String NODE_PRE_DEPLOYING = "pre-deploying";
+ private static final String NODE_DEPLOYING = "deploying";
+ private static final String NODE_DEPLOYED = "deployed";
+ private static final String NODE_PRE_STARTING = "pre-starting";
+ private static final String NODE_STARTING = "starting";
+ private static final String NODE_STARTED = "started";
+ private static final String NODE_PRE_STOPPING = "pre-stopping";
+ private static final String NODE_STOPPING = "stopping";
+ private static final String NODE_PRE_UNDEPLOYING = "pre-undeploying";
+ private static final String NODE_UNDEPLOYING = "undeploying";
+ private Map> lifecyclebindings = new HashMap>();
+
+ public AppLifeCycle()
+ {
+ // Define Default Graph
+
+ // undeployed -> deployed
+ addEdge(NODE_UNDEPLOYED,NODE_PRE_DEPLOYING);
+ addEdge(NODE_PRE_DEPLOYING,NODE_DEPLOYING);
+ addEdge(NODE_DEPLOYING,NODE_DEPLOYED);
+
+ // deployed -> started
+ addEdge(NODE_DEPLOYED,NODE_PRE_STARTING);
+ addEdge(NODE_PRE_STARTING,NODE_STARTING);
+ addEdge(NODE_STARTING,NODE_STARTED);
+
+ // started -> deployed
+ addEdge(NODE_STARTED,NODE_PRE_STOPPING);
+ addEdge(NODE_PRE_STOPPING,NODE_STOPPING);
+ addEdge(NODE_STOPPING,NODE_DEPLOYED);
+
+ // deployed -> undeployed
+ addEdge(NODE_DEPLOYED,NODE_PRE_UNDEPLOYING);
+ addEdge(NODE_PRE_UNDEPLOYING,NODE_UNDEPLOYING);
+ addEdge(NODE_UNDEPLOYING,NODE_UNDEPLOYED);
+ }
+
+ public void addBinding(AppLifeCycle.Binding binding)
+ {
+ for (String nodeName : binding.getBindingTargets())
+ {
+ if (nodeName.equals("*"))
+ {
+ // Special Case: Bind to all Nodes
+ for (Node node : getNodes())
+ {
+ bindToNode(node,binding);
+ }
+ }
+ else
+ {
+ // Bind to specific node
+ Node node = getNodeByName(nodeName);
+ bindToNode(node,binding);
+ }
+ }
+ }
+
+ private void bindToNode(Node node, AppLifeCycle.Binding binding)
+ {
+ List bindings = lifecyclebindings.get(node);
+ if (bindings == null)
+ {
+ bindings = new ArrayList();
+ }
+ bindings.add(binding);
+
+ lifecyclebindings.put(node,bindings);
+ }
+
+ /**
+ * Get all {@link Node} bound objects.
+ *
+ * @return Set of Object(s) for all lifecycle bindings. never null.
+ */
+ public Set getBindings()
+ {
+ Set boundset = new HashSet();
+
+ for (List bindings : lifecyclebindings.values())
+ {
+ boundset.addAll(bindings);
+ }
+
+ return boundset;
+ }
+
+ /**
+ * Get all objects bound to a specific {@link Node}
+ *
+ * @return Set of Object(s) for specific lifecycle bindings. never null.
+ */
+ public Set getBindings(Node node)
+ {
+ Set boundset = new HashSet();
+
+ List bindings = lifecyclebindings.get(node);
+ if (bindings == null)
+ {
+ return boundset;
+ }
+
+ boundset.addAll(bindings);
+
+ return boundset;
+ }
+
+ public Set getBindings(String nodeName)
+ {
+ Node node = getNodeByName(nodeName);
+ return getBindings(node);
+ }
+
+ public void runBindings(Node node, App app, DeploymentManager deploymentManager) throws Throwable
+ {
+ List bindings = this.lifecyclebindings.get(node);
+ if (bindings == null)
+ {
+ return;
+ }
+
+ for (Binding binding : bindings)
+ {
+ Log.info("Calling " + binding.getClass().getName());
+ binding.processBinding(node,app,deploymentManager);
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
new file mode 100644
index 00000000000..6bb8d39d28c
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/AppProvider.java
@@ -0,0 +1,33 @@
+// ========================================================================
+// 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.util.component.LifeCycle;
+
+/**
+ * Object responsible for providing {@link App}s to the {@link DeploymentManager}
+ */
+public interface AppProvider extends LifeCycle
+{
+ /**
+ * Set the Deployment Manager
+ *
+ * @param deploymentManager
+ * @throws IllegalStateException
+ * if the provider {@link #isRunning()}.
+ */
+ void setDeploymentManager(DeploymentManager deploymentManager);
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
new file mode 100644
index 00000000000..6a7941c0614
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java
@@ -0,0 +1,561 @@
+// ========================================================================
+// 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.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.eclipse.jetty.deploy.bindings.StandardDeployer;
+import org.eclipse.jetty.deploy.bindings.StandardStarter;
+import org.eclipse.jetty.deploy.bindings.StandardStopper;
+import org.eclipse.jetty.deploy.bindings.StandardUndeployer;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.deploy.support.ConfigurationManager;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.util.AttributesMap;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+
+/**
+ * The Deployment Manager.
+ *
+ * Responsibilities:
+ *
+ *
+ *
+ *
Tracking Apps and their LifeCycle Location
+ *
Managing AppProviders and the Apps that they provide.
+ *
Executing AppLifeCycle on App based on current and desired LifeCycle Location.
+ *
+ *
+ *
+ */
+public class DeploymentManager extends AbstractLifeCycle
+{
+ /**
+ * Represents a single tracked app within the deployment manager.
+ */
+ public class AppEntry
+ {
+ /**
+ * Version of the app.
+ *
+ * Note: Auto-increments on each {@link DeploymentManager#addApp(App)}
+ */
+ private int version;
+
+ /**
+ * The app being tracked.
+ */
+ private App app;
+
+ /**
+ * The lifecycle node location of this App
+ */
+ private Node lifecyleNode;
+
+ /**
+ * Tracking the various AppState timestamps (in system milliseconds)
+ */
+ private Map stateTimestamps = new HashMap();
+
+ public App getApp()
+ {
+ return app;
+ }
+
+ public Node getLifecyleNode()
+ {
+ return lifecyleNode;
+ }
+
+ public Map getStateTimestamps()
+ {
+ return stateTimestamps;
+ }
+
+ public int getVersion()
+ {
+ return version;
+ }
+
+ void setLifeCycleNode(Node node)
+ {
+ this.lifecyleNode = node;
+ this.stateTimestamps.put(node,Long.valueOf(System.currentTimeMillis()));
+ }
+ }
+
+ private List providers = new ArrayList();
+ private AppLifeCycle lifecycle = new AppLifeCycle();
+ private LinkedList apps = new LinkedList();
+ private AttributesMap contextAttributes = new AttributesMap();
+ private ConfigurationManager configurationManager;
+ private ContextHandlerCollection contexts;
+ private boolean useStandardBindings = true;
+ private String defaultLifeCycleGoal = "started";
+
+ /**
+ * Receive an app for processing.
+ *
+ * Most commonly used by the various {@link AppProvider} implementations.
+ */
+ public void addApp(App app)
+ {
+ Log.info("App Added: " + app.getOriginId());
+ AppEntry entry = new AppEntry();
+ entry.app = app;
+ entry.setLifeCycleNode(lifecycle.getNodeByName("undeployed"));
+ apps.add(entry);
+
+ if (defaultLifeCycleGoal != null)
+ {
+ // Immediately attempt to go to default lifecycle state
+ this.requestAppGoal(entry,defaultLifeCycleGoal);
+ }
+ }
+
+ public void addAppProvider(AppProvider provider)
+ {
+ providers.add(provider);
+ if (isStarted() || isStarting())
+ {
+ startAppProvider(provider);
+ }
+ }
+
+ public void addLifeCycleBinding(AppLifeCycle.Binding binding)
+ {
+ lifecycle.addBinding(binding);
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ if (useStandardBindings)
+ {
+ Log.info("Using standard bindings");
+ addLifeCycleBinding(new StandardDeployer());
+ addLifeCycleBinding(new StandardStarter());
+ addLifeCycleBinding(new StandardStopper());
+ addLifeCycleBinding(new StandardUndeployer());
+ }
+
+ Log.info("Starting all Providers: " + providers.size());
+ // Start all of the AppProviders
+ for (AppProvider provider : providers)
+ {
+ startAppProvider(provider);
+ }
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ Log.info("Stopping all Providers: " + providers.size());
+
+ // Stop all of the AppProviders
+ for (AppProvider provider : providers)
+ {
+ try
+ {
+ provider.stop();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Unable to start AppProvider",e);
+ }
+ }
+ super.doStop();
+ }
+
+ private AppEntry findAppByContextId(String contextId)
+ {
+ if (contextId == null)
+ {
+ return null;
+ }
+
+ for (AppEntry entry : apps)
+ {
+ if (contextId.equals(entry.app.getContextId()))
+ {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ private AppEntry findAppByOriginId(String originId)
+ {
+ if (originId == null)
+ {
+ return null;
+ }
+
+ for (AppEntry entry : apps)
+ {
+ if (originId.equals(entry.app.getOriginId()))
+ {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ public App getAppByContextId(String contextId)
+ {
+ AppEntry entry = findAppByContextId(contextId);
+ if (entry == null)
+ {
+ return null;
+ }
+ return entry.app;
+ }
+
+ public App getAppByOriginId(String originId)
+ {
+ AppEntry entry = findAppByOriginId(originId);
+ if (entry == null)
+ {
+ return null;
+ }
+ return entry.app;
+ }
+
+ public Collection getAppEntries()
+ {
+ return apps;
+ }
+
+ public Collection getAppProviders()
+ {
+ return providers;
+ }
+
+ public Collection getApps()
+ {
+ List ret = new ArrayList();
+ for (AppEntry entry : apps)
+ {
+ ret.add(entry.app);
+ }
+ return ret;
+ }
+
+ /**
+ * Get Set of {@link App}s by {@link Node}
+ *
+ * @param state
+ * the state to look for.
+ * @return
+ */
+ public Collection getApps(Node node)
+ {
+ List ret = new ArrayList();
+ for (AppEntry entry : apps)
+ {
+ if (entry.lifecyleNode == node)
+ {
+ ret.add(entry.app);
+ }
+ }
+ return ret;
+ }
+
+ public List getAppsWithSameContext(App app)
+ {
+ List ret = new ArrayList();
+ if (app == null)
+ {
+ return ret;
+ }
+
+ String contextId = app.getContextId();
+ if (contextId == null)
+ {
+ // No context? Likely not deployed or started yet.
+ return ret;
+ }
+
+ for (AppEntry entry : apps)
+ {
+ if (entry.app.equals(app))
+ {
+ // Its the input app. skip it.
+ // TODO: is this filter needed?
+ continue;
+ }
+
+ if (contextId.equals(entry.app.getContextId()))
+ {
+ ret.add(entry.app);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Get a contextAttribute that will be set for every Context deployed by this provider.
+ *
+ * @param name
+ * @return
+ */
+ public Object getAttribute(String name)
+ {
+ return contextAttributes.getAttribute(name);
+ }
+
+ public ConfigurationManager getConfigurationManager()
+ {
+ return configurationManager;
+ }
+
+ public AttributesMap getContextAttributes()
+ {
+ return contextAttributes;
+ }
+
+ public ContextHandlerCollection getContexts()
+ {
+ return contexts;
+ }
+
+ public String getDefaultLifeCycleGoal()
+ {
+ return defaultLifeCycleGoal;
+ }
+
+ public AppLifeCycle getLifeCycle()
+ {
+ return lifecycle;
+ }
+
+ public Server getServer()
+ {
+ if (contexts == null)
+ {
+ return null;
+ }
+ return contexts.getServer();
+ }
+
+ /**
+ * Remove the app from the tracking of the DeploymentManager
+ *
+ * @param app
+ * if the app is Unavailable remove it from the deployment manager.
+ */
+ public void removeApp(App app)
+ {
+ ListIterator it = apps.listIterator();
+ while (it.hasNext())
+ {
+ AppEntry entry = it.next();
+ if (entry.app.equals(app) && "undeployed".equals(entry.lifecyleNode.getName()))
+ {
+ Log.info("Remove App: " + entry.app);
+ it.remove();
+ }
+ }
+ }
+
+ public void removeAppProvider(AppProvider provider)
+ {
+ providers.remove(provider);
+ try
+ {
+ provider.stop();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Unable to stop Provider",e);
+ }
+ }
+
+ /**
+ * Remove a contextAttribute that will be set for every Context deployed by this provider.
+ *
+ * @param name
+ */
+ public void removeAttribute(String name)
+ {
+ contextAttributes.removeAttribute(name);
+ }
+
+ /**
+ * Move an {@link App} through the {@link AppLifeCycle} to the desired {@link Node}, executing each lifecycle step
+ * in the process to reach the desired state.
+ *
+ * @param app
+ * the app to move through the process
+ * @param nodeName
+ * the name of the node to attain
+ */
+ public void requestAppGoal(App app, String nodeName)
+ {
+ AppEntry appentry = findAppByContextId(app.getContextId());
+ if (appentry == null)
+ {
+ appentry = findAppByOriginId(app.getOriginId());
+ if (appentry == null)
+ {
+ throw new IllegalStateException("App not being tracked by Deployment Manager: " + app);
+ }
+ }
+ requestAppGoal(appentry,nodeName);
+ }
+
+ /**
+ * Move an {@link App} through the {@link AppLifeCycle} to the desired {@link Node}, executing each lifecycle step
+ * in the process to reach the desired state.
+ *
+ * @param appentry
+ * the internal appentry to move through the process
+ * @param nodeName
+ * the name of the node to attain
+ */
+ private void requestAppGoal(AppEntry appentry, String nodeName)
+ {
+ Node destinationNode = lifecycle.getNodeByName(nodeName);
+ // Compute lifecycle steps
+ List path = lifecycle.findPath(appentry.lifecyleNode,destinationNode);
+ if (path.isEmpty())
+ {
+ // nothing to do. already there.
+ return;
+ }
+
+ // Execute each Node binding. Stopping at any thrown exception.
+ try
+ {
+ Iterator it = path.iterator();
+ if (it.hasNext()) // Any entries?
+ {
+ // The first entry in the path is always the start node
+ // We don't want to run bindings on that entry (again)
+ it.next(); // skip first entry
+ while (it.hasNext())
+ {
+ Node node = it.next();
+ Log.info("Executing Node: " + node);
+ lifecycle.runBindings(node,appentry.app,this);
+ appentry.setLifeCycleNode(node);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ Log.warn("Unable to reach node goal: " + nodeName,t);
+ }
+ }
+
+ /**
+ * Move an {@link App} through the {@link AppLifeCycle} to the desired {@link Node}, executing each lifecycle step
+ * in the process to reach the desired state.
+ *
+ * @param appId
+ * the id of the app to move through the process
+ * @param nodeName
+ * the name of the node to attain
+ */
+ public void requestAppGoal(String appId, String nodeName)
+ {
+ AppEntry appentry = findAppByContextId(appId);
+ if (appentry == null)
+ {
+ appentry = findAppByOriginId(appId);
+ if (appentry == null)
+ {
+ throw new IllegalStateException("App not being tracked by Deployment Manager: " + appId);
+ }
+ }
+ requestAppGoal(appentry,nodeName);
+ }
+
+ /**
+ * Set a contextAttribute that will be set for every Context deployed by this provider.
+ *
+ * @param name
+ * @param value
+ */
+ public void setAttribute(String name, Object value)
+ {
+ contextAttributes.setAttribute(name,value);
+ }
+
+ public void setConfigurationManager(ConfigurationManager configurationManager)
+ {
+ this.configurationManager = configurationManager;
+ }
+
+ public void setContextAttributes(AttributesMap contextAttributes)
+ {
+ this.contextAttributes = contextAttributes;
+ }
+
+ public void setContexts(ContextHandlerCollection contexts)
+ {
+ this.contexts = contexts;
+ }
+
+ public void setDefaultLifeCycleGoal(String defaultLifeCycleState)
+ {
+ this.defaultLifeCycleGoal = defaultLifeCycleState;
+ }
+
+ private void startAppProvider(AppProvider provider)
+ {
+ try
+ {
+ provider.setDeploymentManager(this);
+ provider.start();
+ }
+ catch (Exception e)
+ {
+ Log.warn("Unable to start AppProvider",e);
+ }
+ }
+
+ public void undeployAll()
+ {
+ Log.info("Undeploy (All) started");
+ for (AppEntry appentry : apps)
+ {
+ Log.info("Undeploy: " + appentry);
+ requestAppGoal(appentry,"undeployed");
+ }
+ Log.info("Undeploy (All) completed");
+ }
+
+ public boolean isUseStandardBindings()
+ {
+ return useStandardBindings;
+ }
+
+ public void setUseStandardBindings(boolean useStandardBindings)
+ {
+ this.useStandardBindings = useStandardBindings;
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.java
new file mode 100644
index 00000000000..4e43e86034c
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardDeployer.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.bindings;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+public class StandardDeployer implements AppLifeCycle.Binding
+{
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "deploying" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ ContextHandler handler = app.getContextHandler(deploymentManager);
+ if (handler == null)
+ {
+ throw new NullPointerException("No Handler created for App: " + app);
+ }
+ deploymentManager.getContexts().addHandler(handler);
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
new file mode 100644
index 00000000000..651400614c1
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStarter.java
@@ -0,0 +1,46 @@
+// ========================================================================
+// 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.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.util.log.Log;
+
+public class StandardStarter implements AppLifeCycle.Binding
+{
+ public String[] getBindingTargets() {
+ return new String[] { "starting" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ ContextHandler handler = app.getContextHandler(deploymentManager);
+ if (!handler.isStarted())
+ {
+ handler.start();
+ }
+
+ // Remove other apps at same context
+ for (App other : deploymentManager.getAppsWithSameContext(app))
+ {
+ Log.info("Removing apps with same context: " + other);
+ deploymentManager.requestAppGoal(other,"undeployed");
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
new file mode 100644
index 00000000000..a00b8655417
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardStopper.java
@@ -0,0 +1,40 @@
+// ========================================================================
+// 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.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.server.handler.ContextHandler;
+
+public class StandardStopper implements AppLifeCycle.Binding
+{
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "stopping" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ ContextHandler handler = app.getContextHandler(deploymentManager);
+ if (!handler.isStopped())
+ {
+ handler.stop();
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
new file mode 100644
index 00000000000..1dc33d02aa7
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/bindings/StandardUndeployer.java
@@ -0,0 +1,67 @@
+// ========================================================================
+// 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.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.handler.ContextHandler;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.log.Log;
+
+public class StandardUndeployer implements AppLifeCycle.Binding
+{
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "undeploying" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ ContextHandler handler = app.getContextHandler(deploymentManager);
+ ContextHandlerCollection chcoll = deploymentManager.getContexts();
+
+ recursiveRemoveContext(chcoll,handler);
+
+ deploymentManager.removeApp(app);
+ }
+
+ private void recursiveRemoveContext(HandlerCollection coll, ContextHandler context)
+ {
+ Handler children[] = coll.getHandlers();
+ int originalCount = children.length;
+
+ for (int i = 0, n = children.length; i < n; i++)
+ {
+ Handler child = children[i];
+ Log.info("Child handler: " + child);
+ if (child.equals(context))
+ {
+ Log.info("Removing handler: " + child);
+ coll.removeHandler(child);
+ Log.info(String.format("After removal: %d (originally %d)",coll.getHandlers().length,originalCount));
+ }
+ else if (child instanceof HandlerCollection)
+ {
+ recursiveRemoveContext((HandlerCollection)child,context);
+ }
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Edge.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Edge.java
new file mode 100644
index 00000000000..5e6d07079b8
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Edge.java
@@ -0,0 +1,78 @@
+// ========================================================================
+// 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.graph;
+
+/**
+ * Basic Graph Edge
+ */
+public class Edge
+{
+ private Node from;
+ private Node to;
+
+ public Edge(Node from, Node to)
+ {
+ this.from = from;
+ this.to = to;
+ }
+
+ @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;
+ }
+
+ public Node getFrom()
+ {
+ return from;
+ }
+
+ public Node getTo()
+ {
+ return to;
+ }
+}
\ No newline at end of file
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Graph.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Graph.java
new file mode 100644
index 00000000000..1c0f6446a9b
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Graph.java
@@ -0,0 +1,385 @@
+// ========================================================================
+// 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.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+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;
+
+/**
+ * Basic directed graph implementation
+ */
+public class Graph
+{
+ private class EdgeSearch
+ {
+ private Set seenEdges = new HashSet();
+ private List paths = new ArrayList();
+
+ public EdgeSearch(Node from)
+ {
+ NodePath path = new NodePath();
+ path.add(from);
+ paths.add(path);
+ }
+
+ public void breadthFirst(Node destination)
+ {
+ // Test existing edge endpoints
+ if (hasReachedDestination(destination))
+ {
+ // Found our destination!
+
+ // Now remove the other paths that do not end at the destination
+ ListIterator pathiter = paths.listIterator();
+ while (pathiter.hasNext())
+ {
+ NodePath path = pathiter.next();
+ if (path.lastNode() != destination)
+ {
+ pathiter.remove();
+ }
+ }
+ return;
+ }
+
+ List extrapaths = null;
+
+ // Add next unseen segments to paths.
+ boolean pathsAdded = false;
+
+ for (NodePath path : paths)
+ {
+ List 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();
+ }
+
+ // Split path for other edges
+ for (int i = 1, n = next.size(); i < n; i++)
+ {
+ NodePath split = path.forkPath();
+ // Add segment to split'd path
+ split.add(next.get(i).getTo());
+
+ // Add to extra paths
+ extrapaths.add(split);
+ }
+ }
+
+ // Add edge to current path
+ Edge edge = next.get(0);
+ path.add(edge.getTo());
+
+ // 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);
+ }
+ }
+
+ public NodePath getShortestPath()
+ {
+ NodePath shortest = null;
+ int shortestlen = Integer.MAX_VALUE;
+
+ for (NodePath path : paths)
+ {
+ if (shortest == null)
+ {
+ shortest = path;
+ continue;
+ }
+
+ int len = path.length();
+
+ if (len < shortestlen)
+ {
+ shortest = path;
+ shortestlen = len;
+ }
+ }
+
+ return shortest;
+ }
+
+ private boolean hasReachedDestination(Node destination)
+ {
+ for (NodePath path : paths)
+ {
+ if (path.lastNode() == destination)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private List nextUnseenEdges(NodePath path)
+ {
+ List next = new ArrayList();
+
+ for (Edge edge : findEdgesFrom(path.lastNode()))
+ {
+ if (seenEdges.contains(edge) == false)
+ {
+ next.add(edge);
+ }
+ }
+
+ return next;
+ }
+ }
+
+ private class NodePath implements Iterable
+ {
+ private Stack path;
+
+ public NodePath()
+ {
+ path = new Stack();
+ }
+
+ public void add(Node node)
+ {
+ path.push(node);
+ }
+
+ public NodePath forkPath()
+ {
+ NodePath ep = new NodePath();
+ for (Node node : this)
+ {
+ ep.add(node);
+ }
+ return ep;
+ }
+
+ public Collection getCollection()
+ {
+ return path;
+ }
+
+ public Iterator iterator()
+ {
+ return path.iterator();
+ }
+
+ public Node lastNode()
+ {
+ return path.peek();
+ }
+
+ public int length()
+ {
+ return path.size();
+ }
+ }
+
+ private Set nodes = new HashSet();
+ private Set edges = new HashSet();
+
+ public void addEdge(Edge edge)
+ {
+ this.edges.add(edge);
+ }
+
+ public void addEdge(String from, String to)
+ {
+ Node fromNode = null;
+ Node toNode = null;
+
+ try
+ {
+ fromNode = getNodeByName(from);
+ }
+ catch (NodeNotFoundException e)
+ {
+ fromNode = new Node(from);
+ addNode(fromNode);
+ }
+
+ try
+ {
+ toNode = getNodeByName(to);
+ }
+ catch (NodeNotFoundException e)
+ {
+ toNode = new Node(to);
+ addNode(toNode);
+ }
+
+ Edge edge = new Edge(fromNode,toNode);
+ addEdge(edge);
+ }
+
+ public void addNode(Node node)
+ {
+ this.nodes.add(node);
+ }
+
+ /**
+ * Find all edges that are connected to the specific node, both as an outgoing {@link Edge#getFrom()} or incoming
+ * {@link Edge#getTo()} end point.
+ *
+ * @param node
+ * the node with potential end points
+ * @return the set of edges connected to the node
+ */
+ public Set findEdges(Node node)
+ {
+ Set fromedges = new HashSet();
+
+ for (Edge edge : this.edges)
+ {
+ if ((edge.getFrom() == node) || (edge.getTo() == node))
+ {
+ fromedges.add(edge);
+ }
+ }
+
+ return fromedges;
+ }
+
+ /**
+ * Find all edges that are connected {@link Edge#getFrom()} the specific node.
+ *
+ * @param node
+ * the node with potential edges from it
+ * @return the set of edges from the node
+ */
+ public Set findEdgesFrom(Node from)
+ {
+ Set fromedges = new HashSet();
+
+ for (Edge edge : this.edges)
+ {
+ if (edge.getFrom() == from)
+ {
+ fromedges.add(edge);
+ }
+ }
+
+ return fromedges;
+ }
+
+ /**
+ * Using BFS (Breadth First Search) return the path from a any arbitrary node to any other.
+ *
+ * @param from
+ * the node from
+ * @param to
+ * the node to
+ * @return the path to take
+ */
+ public List findPath(Node from, Node to)
+ {
+ if (from == to)
+ {
+ return Collections.emptyList();
+ }
+
+ // Perform a Breadth First Search (BFS) of the tree.
+ EdgeSearch search = new EdgeSearch(from);
+ search.breadthFirst(to);
+
+ NodePath nodepath = search.getShortestPath();
+ List path = new ArrayList();
+ path.addAll(nodepath.getCollection());
+ return path;
+ }
+
+ public Set getEdges()
+ {
+ return edges;
+ }
+
+ public Node getNodeByName(String name)
+ {
+ for (Node node : nodes)
+ {
+ if (node.getName().equals(name))
+ {
+ return node;
+ }
+ }
+
+ throw new NodeNotFoundException("Unable to find node: " + name);
+ }
+
+ public Set getNodes()
+ {
+ return nodes;
+ }
+
+ public void removeEdge(Edge edge)
+ {
+ this.edges.remove(edge);
+ }
+
+ public void removeEdge(String fromNodeName, String toNodeName)
+ {
+ Node fromNode = getNodeByName(fromNodeName);
+ Node toNode = getNodeByName(toNodeName);
+ Edge edge = new Edge(fromNode,toNode);
+ removeEdge(edge);
+ }
+
+ public void removeNode(Node node)
+ {
+ this.nodes.remove(node);
+ }
+
+ public void setEdges(Set edges)
+ {
+ this.edges = edges;
+ }
+
+ public void setNodes(Set nodes)
+ {
+ this.nodes = nodes;
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/ImpossiblePathException.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/ImpossiblePathException.java
new file mode 100644
index 00000000000..7a9e871dac8
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/ImpossiblePathException.java
@@ -0,0 +1,31 @@
+// ========================================================================
+// 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.graph;
+
+public class ImpossiblePathException extends RuntimeException
+{
+ private static final long serialVersionUID = 8423437443748056467L;
+
+ public ImpossiblePathException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public ImpossiblePathException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Node.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Node.java
new file mode 100644
index 00000000000..ab80ea71bbc
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/Node.java
@@ -0,0 +1,69 @@
+// ========================================================================
+// 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.graph;
+
+/**
+ * Basic Graph Node
+ */
+public class Node
+{
+ private String name;
+
+ public Node(String name)
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Node[" + name + "]";
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((name == null)?0:name.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;
+ Node other = (Node)obj;
+ if (name == null)
+ {
+ if (other.name != null)
+ return false;
+ }
+ else if (!name.equals(other.name))
+ return false;
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/NodeNotFoundException.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/NodeNotFoundException.java
new file mode 100644
index 00000000000..336adf9c4db
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/graph/NodeNotFoundException.java
@@ -0,0 +1,34 @@
+// ========================================================================
+// 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.graph;
+
+/**
+ * NodeNotFoundException for when a referenced node cannot be found.
+ */
+public class NodeNotFoundException extends RuntimeException
+{
+ private static final long serialVersionUID = -7126395440535048386L;
+
+ public NodeNotFoundException(String message, Throwable cause)
+ {
+ super(message,cause);
+ }
+
+ public NodeNotFoundException(String message)
+ {
+ super(message);
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProvider.java
new file mode 100644
index 00000000000..1abc0935c24
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProvider.java
@@ -0,0 +1,226 @@
+// ========================================================================
+// 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 java.io.File;
+import java.io.FilenameFilter;
+import java.util.Collections;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppProvider;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * 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 MonitoredDirAppProvider extends AbstractLifeCycle implements AppProvider, Scanner.DiscreteListener
+{
+ class ExtensionFilenameFilter implements FilenameFilter
+ {
+ boolean acceptXml = true;
+ boolean acceptWar = true;
+
+ public boolean accept(File dir, String name)
+ {
+ String lowername = name.toLowerCase();
+
+ if (acceptXml && (lowername.endsWith(".xml")))
+ {
+ return true;
+ }
+
+ if (acceptWar && (lowername.endsWith(".war")))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private Resource monitoredDir;
+ private Scanner scanner;
+ private int scanInterval = 10;
+ private boolean recursive = false;
+ private boolean extractWars = false;
+ private boolean parentLoaderPriority = false;
+ private String defaultsDescriptor;
+ private DeploymentManager deploymgr;
+ private ExtensionFilenameFilter filenamefilter;
+
+ public MonitoredDirAppProvider()
+ {
+ scanner = new Scanner();
+ filenamefilter = new ExtensionFilenameFilter();
+ }
+
+ private void addConfiguredContextApp(String filename)
+ {
+ String originId = filename;
+ App app = new App(originId,new File(filename));
+ app.setExtractWars(this.extractWars);
+ app.setParentLoaderPriority(this.parentLoaderPriority);
+ app.setDefaultsDescriptor(this.defaultsDescriptor);
+ this.deploymgr.addApp(app);
+ }
+
+ public void fileAdded(String filename) throws Exception
+ {
+ Log.info("fileAdded(" + filename + ")");
+ addConfiguredContextApp(filename);
+ }
+
+ public void fileChanged(String filename) throws Exception
+ {
+ Log.info("fileChanged(" + filename + ")");
+ addConfiguredContextApp(filename);
+ }
+
+ public void fileRemoved(String filename) throws Exception
+ {
+ // TODO: How to determine ID from filename that doesn't exist?
+ /*
+ Log.info("fileRemoved(" + filename + ")");
+ addConfiguredContextApp(filename);
+ */
+ }
+
+ public String getDefaultsDescriptor()
+ {
+ return defaultsDescriptor;
+ }
+
+ public Resource getMonitoredDir()
+ {
+ return monitoredDir;
+ }
+
+ public int getScanInterval()
+ {
+ return scanInterval;
+ }
+
+ public boolean isExtractWars()
+ {
+ return extractWars;
+ }
+
+ public boolean isParentLoaderPriority()
+ {
+ return parentLoaderPriority;
+ }
+
+ public boolean isRecursive()
+ {
+ return recursive;
+ }
+
+ public void setAcceptContextXmlFiles(boolean flag)
+ {
+ filenamefilter.acceptXml = flag;
+ }
+
+ public void setAcceptWarFiles(boolean flag)
+ {
+ filenamefilter.acceptWar = flag;
+ }
+
+ public void setDefaultsDescriptor(String defaultsDescriptor)
+ {
+ this.defaultsDescriptor = defaultsDescriptor;
+ }
+
+ public void setExtractWars(boolean extractWars)
+ {
+ this.extractWars = extractWars;
+ }
+
+ public void setMonitoredDir(Resource contextsDir)
+ {
+ this.monitoredDir = contextsDir;
+ }
+
+ /**
+ * @param dir
+ * Directory to scan for context descriptors or war files
+ */
+ public void setMonitoredDir(String dir)
+ {
+ try
+ {
+ monitoredDir = Resource.newResource(dir);
+ }
+ catch (Exception e)
+ {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public void setParentLoaderPriority(boolean parentLoaderPriority)
+ {
+ this.parentLoaderPriority = parentLoaderPriority;
+ }
+
+ public void setRecursive(boolean recursive)
+ {
+ this.recursive = recursive;
+ }
+
+ public void setScanInterval(int scanInterval)
+ {
+ this.scanInterval = scanInterval;
+ }
+
+ public void setDeploymentManager(DeploymentManager deploymentManager)
+ {
+ this.deploymgr = deploymentManager;
+ }
+
+ @Override
+ protected void doStart() throws Exception
+ {
+ Log.info(this.getClass().getSimpleName() + ".doStart()");
+ if (monitoredDir == null)
+ {
+ throw new IllegalStateException("No configuration dir specified");
+ }
+
+ File scandir = monitoredDir.getFile();
+ Log.info("ScanDir: " + scandir);
+ this.scanner.setScanDirs(Collections.singletonList(scandir));
+ this.scanner.setScanInterval(scanInterval);
+ this.scanner.setRecursive(recursive);
+ this.scanner.setFilenameFilter(filenamefilter);
+ this.scanner.addListener(this);
+ this.scanner.scan();
+ this.scanner.start();
+ Log.info("Started scanner: " + scanner);
+ }
+
+ @Override
+ protected void doStop() throws Exception
+ {
+ Log.info(this.getClass().getSimpleName() + ".doStop()");
+ this.scanner.removeListener(this);
+ this.scanner.stop();
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/ConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/ConfigurationManager.java
new file mode 100644
index 00000000000..c9f7ad56aae
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/ConfigurationManager.java
@@ -0,0 +1,27 @@
+// ========================================================================
+// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.deploy.support;
+
+import java.util.Map;
+
+/**
+ * ConfigurationManager
+ *
+ * Type for allow injection of property values
+ * for replacement in jetty xml files during deployment.
+ */
+public interface ConfigurationManager
+{
+ public Map, ?> getProperties();
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/FileConfigurationManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/FileConfigurationManager.java
new file mode 100644
index 00000000000..e4dc398d5f5
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/support/FileConfigurationManager.java
@@ -0,0 +1,66 @@
+// ========================================================================
+// Copyright (c) 2006-2009 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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.opensource.org/licenses/apache2.0.php
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+
+package org.eclipse.jetty.deploy.support;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.Map;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+/**
+ * FileConfigurationManager
+ *
+ * Supplies properties defined in a file.
+ */
+public class FileConfigurationManager implements ConfigurationManager
+{
+ private Resource _file;
+ private Properties _properties = new Properties();
+
+ public FileConfigurationManager()
+ {
+ }
+
+ public void setFile(String filename) throws MalformedURLException, IOException
+ {
+ _file = Resource.newResource(filename);
+ }
+
+ /**
+ * @see org.eclipse.jetty.deploy.ConfigurationManager#getProperties()
+ */
+ public Map, ?> getProperties()
+ {
+ try
+ {
+ loadProperties();
+ return _properties;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void loadProperties() throws FileNotFoundException, IOException
+ {
+ if (_properties.isEmpty())
+ {
+ _properties.load(_file.getInputStream());
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java
new file mode 100644
index 00000000000..634a14b04fb
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/util/FileID.java
@@ -0,0 +1,76 @@
+// ========================================================================
+// 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.util;
+
+import java.io.File;
+
+/**
+ * Simple, yet surprisingly common utility methods for identifying various file types commonly seen and worked with in a
+ * deployment scenario.
+ */
+public class FileID
+{
+ /**
+ * Is the path a Web Archive?
+ *
+ * @param path
+ * the path to test.
+ * @return True if a .war or .jar or exploded web directory
+ * @see FileID#isWebArchiveFile(File)
+ */
+ public static boolean isWebArchive(File path)
+ {
+ if (path.isFile())
+ {
+ String name = path.getName().toLowerCase();
+ return (name.endsWith(".war") || name.endsWith(".jar"));
+ }
+
+ File webInf = new File(path,"WEB-INF");
+ File webXml = new File(webInf,"web.xml");
+ return webXml.exists() && webXml.isFile();
+ }
+
+ /**
+ * Is the path a Web Archive File (not directory)
+ *
+ * @param path
+ * the path to test.
+ * @return True if a .war or .jar file.
+ * @see FileID#isWebArchive(File)
+ */
+ public static boolean isWebArchiveFile(File path)
+ {
+ if (!path.isFile())
+ {
+ return false;
+ }
+
+ String name = path.getName().toLowerCase();
+ return (name.endsWith(".war") || name.endsWith(".jar"));
+ }
+
+ public static boolean isXmlFile(File path)
+ {
+ if (!path.isFile())
+ {
+ return false;
+ }
+
+ String name = path.getName().toLowerCase();
+ return name.endsWith(".xml");
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/DeployAuditLogBinding.java b/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/DeployAuditLogBinding.java
new file mode 100644
index 00000000000..d2c1988a391
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/DeployAuditLogBinding.java
@@ -0,0 +1,39 @@
+// ========================================================================
+// 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.webapp.deploy;
+
+import java.util.logging.Logger;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+
+public class DeployAuditLogBinding implements AppLifeCycle.Binding
+{
+ private Logger logger = Logger.getLogger("audit.deploy");
+
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "*" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ logger.info("Reached LifeCycle " + node.getName() + " on app " + app.getOriginId());
+ }
+}
diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/WebappVerifierBinding.java b/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/WebappVerifierBinding.java
new file mode 100644
index 00000000000..9ce1f532bf5
--- /dev/null
+++ b/jetty-deploy/src/main/java/org/eclipse/jetty/webapp/deploy/WebappVerifierBinding.java
@@ -0,0 +1,95 @@
+// ========================================================================
+// 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.webapp.deploy;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Collection;
+
+import org.eclipse.jetty.deploy.App;
+import org.eclipse.jetty.deploy.AppLifeCycle;
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.deploy.graph.Node;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.webapp.verifier.RuleSet;
+import org.eclipse.jetty.webapp.verifier.Severity;
+import org.eclipse.jetty.webapp.verifier.Violation;
+import org.eclipse.jetty.webapp.verifier.WebappVerifier;
+
+/**
+ * Deploy Binding for the Webapp Verifier.
+ *
+ * Will verify the webapp being deployed, to ensure that it satisfies the ruleset configured.
+ */
+public class WebappVerifierBinding implements AppLifeCycle.Binding
+{
+ private String rulesetPath;
+
+ public String getRulesetPath()
+ {
+ return rulesetPath;
+ }
+
+ public void setRulesetPath(String rulesetPath)
+ {
+ this.rulesetPath = rulesetPath;
+ }
+
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "pre-deploying" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ File rulesetFile = new File(this.rulesetPath);
+
+ RuleSet ruleset = RuleSet.load(rulesetFile);
+
+ URI warURI = app.getArchivePath().toURI();
+
+ WebappVerifier verifier = ruleset.createWebappVerifier(warURI);
+
+ verifier.visitAll();
+
+ Collection violations = verifier.getViolations();
+ if (violations.size() <= 0)
+ {
+ // Nothing to report.
+ Log.info("Webapp Verifier - All Rules Passed - No Violations");
+ return;
+ }
+
+ boolean haltWebapp = false;
+ Log.info("Webapp Verifier Found " + violations.size() + " violations.");
+
+ for (Violation violation : violations)
+ {
+ if (violation.getSeverity() == Severity.ERROR)
+ {
+ haltWebapp = true;
+ }
+
+ Log.info(violation.toString());
+ }
+
+ if (haltWebapp)
+ {
+ throw new IllegalStateException("Webapp Failed Webapp Verification");
+ }
+ }
+}
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png
new file mode 100644
index 00000000000..071c7472353
Binary files /dev/null and b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.png differ
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg
new file mode 100644
index 00000000000..dc6974c97e4
--- /dev/null
+++ b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/AppLifecycle.svg
@@ -0,0 +1,482 @@
+
+
+
+
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.png b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.png
new file mode 100644
index 00000000000..b3c0a5ba18c
Binary files /dev/null and b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.png differ
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.svg b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.svg
new file mode 100644
index 00000000000..68a51bfe4a2
--- /dev/null
+++ b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager.svg
@@ -0,0 +1,570 @@
+
+
+
+
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.png b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.png
new file mode 100644
index 00000000000..4b96a6bf10d
Binary files /dev/null and b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.png differ
diff --git a/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.svg b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.svg
new file mode 100644
index 00000000000..7185759d5f6
--- /dev/null
+++ b/jetty-deploy/src/main/javadoc/org/eclipse/jetty/deploy/doc-files/DeploymentManager_Roles.svg
@@ -0,0 +1,389 @@
+
+
+
+
diff --git a/jetty-deploy/src/main/javadoc/overview.html b/jetty-deploy/src/main/javadoc/overview.html
new file mode 100644
index 00000000000..689d4f41ba7
--- /dev/null
+++ b/jetty-deploy/src/main/javadoc/overview.html
@@ -0,0 +1,9 @@
+
+
+
+ Jetty DeploymentManager API Overview
+
+
+ Short overview of the API.
+
+
diff --git a/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt b/jetty-deploy/src/main/resources/org/eclipse/jetty/deploy/lifecycle-bindings.txt
new file mode 100644
index 00000000000..ed00104daf3
--- /dev/null
+++ b/jetty-deploy/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/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java
new file mode 100644
index 00000000000..573d1059bc0
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCyclePathCollector.java
@@ -0,0 +1,75 @@
+// ========================================================================
+// 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.graph.Node;
+import org.junit.Assert;
+
+/**
+ * Binds to all lifecycle nodes, and tracks the order of the lifecycle nodes for testing purposes.
+ */
+public class AppLifeCyclePathCollector implements AppLifeCycle.Binding
+{
+ List actualOrder = new ArrayList();
+
+ public void clear()
+ {
+ actualOrder.clear();
+ }
+
+ public List getCapturedPath()
+ {
+ return actualOrder;
+ }
+
+ public String[] getBindingTargets()
+ {
+ return new String[]
+ { "*" };
+ }
+
+ public void processBinding(Node node, App app, DeploymentManager deploymentManager) throws Exception
+ {
+ actualOrder.add(node);
+ }
+
+ public void assertExpected(String msg, List expectedOrder)
+ {
+ if (expectedOrder.size() != actualOrder.size())
+ {
+ System.out.println("/* Expected Path */");
+ for (String path : expectedOrder)
+ {
+ System.out.println(path);
+ }
+ System.out.println("/* Actual Path */");
+ for (Node path : actualOrder)
+ {
+ System.out.println(path.getName());
+ }
+
+ Assert.assertEquals(msg + " / count",expectedOrder.size(),actualOrder.size());
+ }
+
+ for (int i = 0, n = expectedOrder.size(); i < n; i++)
+ {
+ Assert.assertEquals(msg + "[" + i + "]",expectedOrder.get(i),actualOrder.get(i).getName());
+ }
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java
new file mode 100644
index 00000000000..0f3dd330265
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/AppLifeCycleTest.java
@@ -0,0 +1,217 @@
+// ========================================================================
+// 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.graph.Node;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Just an overly picky test case to validate the potential paths.
+ */
+public class AppLifeCycleTest
+{
+ private void assertNoPath(String from, String to)
+ {
+ assertPath(from,to,new ArrayList());
+ }
+
+ private void assertPath(AppLifeCycle lifecycle, String from, String to, List expected)
+ {
+ Node fromNode = lifecycle.getNodeByName(from);
+ Node toNode = lifecycle.getNodeByName(to);
+ List actual = lifecycle.findPath(fromNode,toNode);
+ String msg = "LifeCycle path from " + from + " to " + to;
+ Assert.assertNotNull(msg + " should never be null",actual);
+
+ if (expected.size() != actual.size())
+ {
+ System.out.println();
+ System.out.printf("/* from '%s' -> '%s' */%n",from,to);
+ System.out.println("/* Expected Path */");
+ for (String path : expected)
+ {
+ System.out.println(path);
+ }
+ System.out.println("/* Actual Path */");
+ for (Node path : actual)
+ {
+ System.out.println(path.getName());
+ }
+
+ Assert.assertEquals(msg + " / count",expected.size(),actual.size());
+ }
+
+ for (int i = 0, n = expected.size(); i < n; i++)
+ {
+ Assert.assertEquals(msg + "[" + i + "]",expected.get(i),actual.get(i).getName());
+ }
+ }
+
+ private void assertPath(String from, String to, List expected)
+ {
+ AppLifeCycle lifecycle = new AppLifeCycle();
+ assertPath(lifecycle,from,to,expected);
+ }
+
+ @Test
+ public void testFindPath_Deployed_Deployed()
+ {
+ assertNoPath("deployed","deployed");
+ }
+
+ @Test
+ public void testFindPath_Deployed_Started()
+ {
+ List expected = new ArrayList();
+ expected.add("deployed");
+ expected.add("pre-starting");
+ expected.add("starting");
+ expected.add("started");
+ assertPath("deployed","started",expected);
+ }
+
+ @Test
+ public void testFindPath_Deployed_Undeployed()
+ {
+ List expected = new ArrayList();
+ expected.add("deployed");
+ expected.add("pre-undeploying");
+ expected.add("undeploying");
+ expected.add("undeployed");
+ assertPath("deployed","undeployed",expected);
+ }
+
+ @Test
+ public void testFindPath_Started_Deployed()
+ {
+ List expected = new ArrayList();
+ expected.add("started");
+ expected.add("pre-stopping");
+ expected.add("stopping");
+ expected.add("deployed");
+ assertPath("started","deployed",expected);
+ }
+
+ @Test
+ public void testFindPath_Started_Started()
+ {
+ assertNoPath("started","started");
+ }
+
+ @Test
+ public void testFindPath_Started_Undeployed()
+ {
+ List expected = new ArrayList();
+ expected.add("started");
+ expected.add("pre-stopping");
+ expected.add("stopping");
+ expected.add("deployed");
+ expected.add("pre-undeploying");
+ expected.add("undeploying");
+ expected.add("undeployed");
+ assertPath("started","undeployed",expected);
+ }
+
+ @Test
+ public void testFindPath_Undeployed_Deployed()
+ {
+ List expected = new ArrayList();
+ expected.add("undeployed");
+ expected.add("pre-deploying");
+ expected.add("deploying");
+ expected.add("deployed");
+ assertPath("undeployed","deployed",expected);
+ }
+
+ @Test
+ public void testFindPath_Undeployed_Started()
+ {
+ List expected = new ArrayList();
+ expected.add("undeployed");
+ expected.add("pre-deploying");
+ expected.add("deploying");
+ expected.add("deployed");
+ expected.add("pre-starting");
+ expected.add("starting");
+ expected.add("started");
+ assertPath("undeployed","started",expected);
+ }
+
+ @Test
+ public void testFindPath_Undeployed_Uavailable()
+ {
+ assertNoPath("undeployed","undeployed");
+ }
+
+ /**
+ * Request multiple lifecycle paths with a single lifecycle instance. Just to ensure that there is no state
+ * maintained between {@link AppLifeCycle#findPath(Node, Node)} requests.
+ */
+ @Test
+ public void testFindPathMultiple()
+ {
+ AppLifeCycle lifecycle = new AppLifeCycle();
+ List expected = new ArrayList();
+
+ lifecycle.removeEdge("deployed","pre-starting");
+ lifecycle.addEdge("deployed","staging");
+ lifecycle.addEdge("staging","staged");
+ lifecycle.addEdge("staged","pre-starting");
+
+ // Deployed -> Deployed
+ expected.clear();
+ assertPath(lifecycle,"deployed","deployed",expected);
+
+ // Deployed -> Staged
+ expected.clear();
+ expected.add("deployed");
+ expected.add("staging");
+ expected.add("staged");
+ assertPath(lifecycle,"deployed","staged",expected);
+
+ // Staged -> Undeployed
+ expected.clear();
+ expected.add("staged");
+ expected.add("pre-starting");
+ expected.add("starting");
+ expected.add("started");
+ expected.add("pre-stopping");
+ expected.add("stopping");
+ expected.add("deployed");
+ expected.add("pre-undeploying");
+ expected.add("undeploying");
+ expected.add("undeployed");
+ assertPath(lifecycle,"staged","undeployed",expected);
+
+ // Undeployed -> Started
+ expected.clear();
+ expected.add("undeployed");
+ expected.add("pre-deploying");
+ expected.add("deploying");
+ expected.add("deployed");
+ expected.add("staging");
+ expected.add("staged");
+ expected.add("pre-starting");
+ expected.add("starting");
+ expected.add("started");
+ assertPath(lifecycle,"undeployed","started",expected);
+ }
+
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java
new file mode 100644
index 00000000000..d306ab50737
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerLifeCyclePathTest.java
@@ -0,0 +1,83 @@
+// ========================================================================
+// 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.server.handler.ContextHandlerCollection;
+import org.junit.Test;
+
+public class DeploymentManagerLifeCyclePathTest
+{
+ @Test
+ public void testStateTransition_NewToDeployed() throws Exception
+ {
+ DeploymentManager depman = new DeploymentManager();
+ depman.setDefaultLifeCycleGoal(null); // no default
+ AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
+ MockAppProvider mockProvider = new MockAppProvider();
+
+ depman.addLifeCycleBinding(pathtracker);
+ depman.addAppProvider(mockProvider);
+ depman.setContexts(new ContextHandlerCollection());
+
+ // Start DepMan
+ depman.start();
+
+ // Trigger new App
+ mockProvider.findWebapp("foo-webapp-1.war");
+
+ App app = depman.getAppByOriginId("mock-foo-webapp-1.war");
+
+ // Request Deploy of App
+ depman.requestAppGoal(app,"deployed");
+
+ // Setup Expectations.
+ List expected = new ArrayList();
+ // SHOULD NOT SEE THIS NODE VISITED - expected.add("undeployed");
+ expected.add("pre-deploying");
+ expected.add("deploying");
+ expected.add("deployed");
+
+ pathtracker.assertExpected("Test StateTransition / New -> Deployed",expected);
+ }
+
+ @Test
+ public void testStateTransition_Receive() throws Exception
+ {
+ DeploymentManager depman = new DeploymentManager();
+ depman.setDefaultLifeCycleGoal(null); // no default
+ AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
+ MockAppProvider mockProvider = new MockAppProvider();
+
+ depman.addLifeCycleBinding(pathtracker);
+ depman.addAppProvider(mockProvider);
+
+ // Start DepMan
+ depman.start();
+
+ // Trigger new App
+ mockProvider.findWebapp("foo-webapp-1.war");
+
+ // Perform no goal request.
+
+ // Setup Expectations.
+ List expected = new ArrayList();
+
+ pathtracker.assertExpected("Test StateTransition / New only",expected);
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
new file mode 100644
index 00000000000..96f26a19027
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/DeploymentManagerTest.java
@@ -0,0 +1,87 @@
+package org.eclipse.jetty.deploy;
+
+import java.util.Collection;
+import java.util.Set;
+
+import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DeploymentManagerTest
+{
+ @Test
+ public void testReceiveApp() throws Exception
+ {
+ DeploymentManager depman = new DeploymentManager();
+ depman.setDefaultLifeCycleGoal(null); // no default
+ AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
+ MockAppProvider mockProvider = new MockAppProvider();
+
+ depman.addLifeCycleBinding(pathtracker);
+ depman.addAppProvider(mockProvider);
+
+ // Start DepMan
+ depman.start();
+
+ // Trigger new App
+ mockProvider.findWebapp("foo-webapp-1.war");
+
+ // Test app tracking
+ Collection apps = depman.getApps();
+ Assert.assertNotNull("Should never be null",apps);
+ Assert.assertEquals("Expected App Count",1,apps.size());
+
+ // Test app get
+ App actual = depman.getAppByOriginId("mock-foo-webapp-1.war");
+ Assert.assertNotNull("Should have gotten app (by id)",actual);
+ Assert.assertEquals("Should have gotten app (by id)","mock-foo-webapp-1.war",actual.getOriginId());
+ }
+
+ @Test
+ public void testBinding()
+ {
+ AppLifeCyclePathCollector pathtracker = new AppLifeCyclePathCollector();
+ DeploymentManager depman = new DeploymentManager();
+ depman.addLifeCycleBinding(pathtracker);
+
+ Set allbindings = depman.getLifeCycle().getBindings();
+ Assert.assertNotNull("All Bindings should never be null",allbindings);
+ Assert.assertEquals("All Bindings.size",1,allbindings.size());
+
+ Set deploybindings = depman.getLifeCycle().getBindings("deploying");
+ Assert.assertNotNull("'deploying' Bindings should not be null",deploybindings);
+ Assert.assertEquals("'deploying' Bindings.size",1,deploybindings.size());
+ }
+
+ @Test
+ public void testXmlConfigured() throws Exception
+ {
+ XmlConfiguredJetty jetty = null;
+ try
+ {
+ jetty = new XmlConfiguredJetty();
+ jetty.addConfiguration("jetty.xml");
+ jetty.addConfiguration("jetty-deploymgr-contexts.xml");
+
+ // Should not throw an Exception
+ jetty.load();
+
+ // Start it
+ jetty.start();
+ }
+ finally
+ {
+ if (jetty != null)
+ {
+ try
+ {
+ jetty.stop();
+ }
+ catch (Exception ignore)
+ {
+ // ignore
+ }
+ }
+ }
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java
new file mode 100644
index 00000000000..2fd52d5ea3a
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/MockAppProvider.java
@@ -0,0 +1,45 @@
+// ========================================================================
+// 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.deploy.test.MavenTestingUtils;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+
+public class MockAppProvider extends AbstractLifeCycle implements AppProvider
+{
+ private DeploymentManager deployMan;
+ private File webappsDir;
+
+ public void setDeploymentManager(DeploymentManager deploymentManager)
+ {
+ this.deployMan = deploymentManager;
+ }
+
+ @Override
+ public void doStart()
+ {
+ this.webappsDir = MavenTestingUtils.getTestResourceDir("webapps");
+ }
+
+ public void findWebapp(String name)
+ {
+ File war = new File(webappsDir,name);
+ App app = new App("mock-" + name,war);
+ this.deployMan.addApp(app);
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderRuntimeUpdatesTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderRuntimeUpdatesTest.java
new file mode 100644
index 00000000000..ab284f81c57
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderRuntimeUpdatesTest.java
@@ -0,0 +1,116 @@
+// ========================================================================
+// 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 java.io.IOException;
+
+import org.eclipse.jetty.deploy.test.XmlConfiguredJetty;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Similar in scope to {@link MonitoredDirAppProviderStartupTest}, except is concerned with the modification of existing
+ * deployed webapps due to incoming changes identified by the {@link MonitoredDirAppProvider}.
+ */
+public class MonitoredDirAppProviderRuntimeUpdatesTest
+{
+ private static XmlConfiguredJetty jetty;
+
+ @Before
+ public void setupEnvironment() throws Exception
+ {
+ jetty = new XmlConfiguredJetty();
+ jetty.addConfiguration("jetty.xml");
+ jetty.addConfiguration("jetty-deploymgr-contexts.xml");
+
+ // Should not throw an Exception
+ jetty.load();
+
+ // Start it
+ jetty.start();
+ }
+
+ @After
+ public void teardownEnvironment() throws Exception
+ {
+ // Stop jetty.
+ jetty.stop();
+ }
+
+ /**
+ * Simple webapp deployment after startup of server.
+ */
+ @Test
+ public void testAfterStartupContext() throws IOException
+ {
+ jetty.copyWebapp("foo-webapp-1.war","foo.war");
+ jetty.copyContext("foo.xml","foo.xml");
+
+ jetty.waitForDirectoryScan();
+
+ jetty.assertWebAppContextsExists("/foo");
+ }
+
+ /**
+ * Simple webapp deployment after startup of server, and then removal of the webapp.
+ */
+ @Test
+ public void testAfterStartupThenRemoveContext() throws IOException
+ {
+ jetty.copyWebapp("foo-webapp-1.war","foo.war");
+ jetty.copyContext("foo.xml","foo.xml");
+
+ jetty.waitForDirectoryScan();
+
+ jetty.assertWebAppContextsExists("/foo");
+
+ jetty.removeContext("foo.xml");
+
+ jetty.waitForDirectoryScan();
+
+ // FIXME: hot undeploy with removal not working! - jetty.assertNoWebAppContexts();
+ }
+
+ /**
+ * Simple webapp deployment after startup of server, and then removal of the webapp.
+ */
+ @Test
+ public void testAfterStartupThenUpdateContext() throws IOException
+ {
+ jetty.copyWebapp("foo-webapp-1.war","foo.war");
+ jetty.copyContext("foo.xml","foo.xml");
+
+ jetty.waitForDirectoryScan();
+
+ jetty.assertWebAppContextsExists("/foo");
+
+ // Test that webapp response contains "-1"
+ jetty.assertResponseContains("/foo/info","FooServlet-1");
+
+ System.out.println("Updating war files");
+ jetty.copyContext("foo.xml","foo.xml"); // essentially "touch" the context xml
+ jetty.copyWebapp("foo-webapp-2.war","foo.war");
+
+ // This should result in the existing foo.war being replaced with the new foo.war
+ jetty.waitForDirectoryScan();
+ jetty.waitForDirectoryScan();
+ jetty.assertWebAppContextsExists("/foo");
+
+ // Test that webapp response contains "-2"
+ jetty.assertResponseContains("/foo/info","FooServlet-2");
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderStartupTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderStartupTest.java
new file mode 100644
index 00000000000..aa2abfe7314
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/providers/MonitoredDirAppProviderStartupTest.java
@@ -0,0 +1,62 @@
+// ========================================================================
+// 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.test.XmlConfiguredJetty;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Tests {@link MonitoredDirAppProvider} as it starts up for the first time.
+ */
+public class MonitoredDirAppProviderStartupTest
+{
+ private static XmlConfiguredJetty jetty;
+
+ @BeforeClass
+ public static void setupEnvironment() throws Exception
+ {
+ jetty = new XmlConfiguredJetty();
+ jetty.addConfiguration("jetty.xml");
+ jetty.addConfiguration("jetty-deploymgr-contexts.xml");
+
+ // Setup initial context
+ jetty.copyContext("foo.xml","foo.xml");
+ jetty.copyWebapp("foo-webapp-1.war","foo.war");
+
+ // Should not throw an Exception
+ jetty.load();
+
+ // Start it
+ jetty.start();
+ }
+
+ @AfterClass
+ public static void teardownEnvironment() throws Exception
+ {
+ // Stop jetty.
+ jetty.stop();
+ }
+
+ @Test
+ public void testStartupContext()
+ {
+ // Check Server for Handlers
+ jetty.printHandlers(System.out);
+ jetty.assertWebAppContextsExists("/foo");
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/MavenTestingUtils.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/MavenTestingUtils.java
new file mode 100644
index 00000000000..cb9dfceb587
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/MavenTestingUtils.java
@@ -0,0 +1,219 @@
+// ========================================================================
+// 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.test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jetty.util.IO;
+
+/**
+ * Common utility methods for working with JUnit tests cases in a maven friendly way.
+ */
+public class MavenTestingUtils
+{
+ private static File basedir;
+ private static File testResourcesDir;
+ private static File targetDir;
+
+ // private static Boolean surefireRunning;
+
+ public static File getBasedir()
+ {
+ if (basedir == null)
+ {
+ String cwd = System.getProperty("basedir");
+
+ if (cwd == null)
+ {
+ cwd = System.getProperty("user.dir");
+ }
+
+ basedir = new File(cwd);
+ }
+
+ return basedir;
+ }
+
+ /**
+ * Get the directory to the /target directory for this project.
+ *
+ * @return the directory path to the target directory.
+ */
+ public static File getTargetDir()
+ {
+ if (targetDir == null)
+ {
+ targetDir = new File(getBasedir(),"target");
+ PathAssert.assertDirExists("Target Dir",targetDir);
+ }
+ return targetDir;
+ }
+
+ /**
+ * Create a {@link File} object for a path in the /target directory.
+ *
+ * @param path
+ * the path desired, no validation of existence is performed.
+ * @return the File to the path.
+ */
+ public static File getTargetFile(String path)
+ {
+ return new File(getTargetDir(),path.replace("/",File.separator));
+ }
+
+ public static File getTargetTestingDir()
+ {
+ File dir = new File(getTargetDir(),"testing");
+ if (!dir.exists())
+ {
+ dir.mkdirs();
+ }
+ return dir;
+ }
+
+ /**
+ * Get a dir in /target/ that uses the JUnit 3.x {@link TestCase#getName()} to make itself unique.
+ *
+ * @param test
+ * the junit 3.x testcase to base this new directory on.
+ * @return
+ */
+ public static File getTargetTestingDir(TestCase test)
+ {
+ return getTargetTestingDir(test.getName());
+ }
+
+ /**
+ * Get a dir in /target/ that uses the an arbitrary name.
+ *
+ * @param testname
+ * the testname to create directory against.
+ * @return
+ */
+ public static File getTargetTestingDir(String testname)
+ {
+ File dir = new File(getTargetDir(),"test-" + testname);
+ if (!dir.exists())
+ {
+ dir.mkdirs();
+ }
+ return dir;
+ }
+
+ /**
+ * Get a dir from the src/test/resource directory.
+ *
+ * @param name
+ * the name of the path to get (it must exist as a dir)
+ * @return the dir in src/test/resource
+ */
+ public static File getTestResourceDir(String name)
+ {
+ File dir = new File(getTestResourcesDir(),name);
+ PathAssert.assertDirExists("Test Resource Dir",dir);
+ return dir;
+ }
+
+ /**
+ * Get a file from the src/test/resource directory.
+ *
+ * @param name
+ * the name of the path to get (it must exist as a file)
+ * @return the file in src/test/resource
+ */
+ public static File getTestResourceFile(String name)
+ {
+ File file = new File(getTestResourcesDir(),name);
+ PathAssert.assertFileExists("Test Resource File",file);
+ return file;
+ }
+
+ /**
+ * Get a path resource (File or Dir) from the src/test/resource directory.
+ *
+ * @param name
+ * the name of the path to get (it must exist)
+ * @return the path in src/test/resource
+ */
+ public static File getTestResourcePath(String name)
+ {
+ File path = new File(getTestResourcesDir(),name);
+ PathAssert.assertExists("Test Resource Path",path);
+ return path;
+ }
+
+ /**
+ * Get the directory to the src/test/resource directory
+ *
+ * @return the directory {@link File} to the src/test/resources directory
+ */
+ public static File getTestResourcesDir()
+ {
+ if (testResourcesDir == null)
+ {
+ testResourcesDir = new File(basedir,"src/test/resources".replace("/",File.separator));
+ PathAssert.assertDirExists("Test Resources Dir",testResourcesDir);
+ }
+ return testResourcesDir;
+ }
+
+ /**
+ * Read the contents of a file into a String and return it.
+ *
+ * @param file
+ * the file to read.
+ * @return the contents of the file.
+ * @throws IOException
+ * if unable to read the file.
+ */
+ public static String readToString(File file) throws IOException
+ {
+ FileReader reader = null;
+ try
+ {
+ reader = new FileReader(file);
+ return IO.toString(reader);
+ }
+ finally
+ {
+ IO.close(reader);
+ }
+ }
+
+ /*
+ public static boolean isSurefireExecuting()
+ {
+ if (surefireRunning == null)
+ {
+ String val = System.getProperty("surefire.test.class.path");
+ if (val != null)
+ {
+ surefireRunning = Boolean.TRUE;
+ }
+ else
+ {
+ surefireRunning = Boolean.FALSE;
+ }
+ }
+
+ return surefireRunning;
+ }
+ */
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/PathAssert.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/PathAssert.java
new file mode 100644
index 00000000000..e4eaedc6a06
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/PathAssert.java
@@ -0,0 +1,40 @@
+// ========================================================================
+// 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.test;
+
+import java.io.File;
+
+import org.junit.Assert;
+
+public class PathAssert
+{
+ public static void assertDirExists(String msg, File path)
+ {
+ assertExists(msg,path);
+ Assert.assertTrue(msg + " path should be a Dir : " + path.getAbsolutePath(),path.isDirectory());
+ }
+
+ public static void assertFileExists(String msg, File path)
+ {
+ assertExists(msg,path);
+ Assert.assertTrue(msg + " path should be a File : " + path.getAbsolutePath(),path.isFile());
+ }
+
+ public static void assertExists(String msg, File path)
+ {
+ Assert.assertTrue(msg + " path should exist: " + path.getAbsolutePath(),path.exists());
+ }
+}
diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
new file mode 100644
index 00000000000..14a2338286d
--- /dev/null
+++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/test/XmlConfiguredJetty.java
@@ -0,0 +1,441 @@
+// ========================================================================
+// 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.test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.jetty.deploy.DeploymentManager;
+import org.eclipse.jetty.http.HttpSchemes;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+import org.junit.Assert;
+
+/**
+ * Allows for setting up a Jetty server for testing based on XML configuration files.
+ */
+public class XmlConfiguredJetty
+{
+ private List xmlConfigurations;
+ private Properties properties = new Properties();
+ private Server server;
+ private int serverPort;
+ private String scheme = HttpSchemes.HTTP;
+ private File jettyHome;
+
+ public XmlConfiguredJetty() throws IOException
+ {
+ String testname = new Throwable().getStackTrace()[1].getClassName();
+
+ xmlConfigurations = new ArrayList();
+ properties = new Properties();
+
+ jettyHome = MavenTestingUtils.getTargetTestingDir(testname);
+ deleteContents(jettyHome); // Ensure that we are working with a pristene directory
+ // Prepare Jetty.Home (Test) dir
+ jettyHome.mkdirs();
+
+ File logsDir = new File(jettyHome,"logs");
+ logsDir.mkdirs();
+
+ File etcDir = new File(jettyHome,"etc");
+ etcDir.mkdirs();
+ IO.copyFile(MavenTestingUtils.getTestResourceFile("etc/realm.properties"),new File(etcDir,"realm.properties"));
+ IO.copyFile(MavenTestingUtils.getTestResourceFile("etc/webdefault.xml"),new File(etcDir,"webdefault.xml"));
+
+ File contextsDir = new File(jettyHome,"contexts");
+ if (contextsDir.exists())
+ {
+ deleteContents(contextsDir);
+ }
+ contextsDir.mkdirs();
+ File webappsDir = new File(jettyHome,"webapps");
+ if (webappsDir.exists())
+ {
+ deleteContents(webappsDir);
+ }
+ webappsDir.mkdirs();
+ File tmpDir = new File(jettyHome,"tmp");
+ tmpDir.mkdirs();
+
+ // Setup properties
+ System.setProperty("java.io.tmpdir",tmpDir.getAbsolutePath());
+ properties.setProperty("jetty.home",jettyHome.getAbsolutePath());
+ System.setProperty("jetty.home",jettyHome.getAbsolutePath());
+ properties.setProperty("test.basedir",MavenTestingUtils.getBasedir().getAbsolutePath());
+ properties.setProperty("test.resourcesdir",MavenTestingUtils.getTestResourcesDir().getAbsolutePath());
+ properties.setProperty("test.webapps",webappsDir.getAbsolutePath());
+ properties.setProperty("test.targetdir",MavenTestingUtils.getTargetDir().getAbsolutePath());
+
+ // Write out configuration for use by ConfigurationManager.
+ File testConfig = MavenTestingUtils.getTargetFile("xml-configured-jetty.properties");
+ FileOutputStream out = new FileOutputStream(testConfig);
+ properties.store(out,"Generated by " + XmlConfiguredJetty.class.getName());
+ }
+
+ public void addConfiguration(File xmlConfigFile) throws MalformedURLException
+ {
+ xmlConfigurations.add(xmlConfigFile.toURI().toURL());
+ }
+
+ public void addConfiguration(String testConfigName) throws MalformedURLException
+ {
+ addConfiguration(MavenTestingUtils.getTestResourceFile(testConfigName));
+ }
+
+ public void addConfiguration(URL xmlConfig)
+ {
+ xmlConfigurations.add(xmlConfig);
+ }
+
+ public void assertNoWebAppContexts()
+ {
+ List contexts = getWebAppContexts();
+ if (contexts.size() > 0)
+ {
+ for (WebAppContext context : contexts)
+ {
+ System.out.println("WebAppContext should not exist:\n" + toString(context));
+ }
+ Assert.assertEquals("Contexts.size",0,contexts.size());
+ }
+ }
+
+ public String getResponse(String path) throws IOException
+ {
+ URI destUri = getServerURI().resolve(path);
+ URL url = destUri.toURL();
+
+ URLConnection conn = url.openConnection();
+
+ InputStream in = null;
+ try
+ {
+ in = conn.getInputStream();
+ return IO.toString(in);
+ }
+ finally
+ {
+ IO.close(in);
+ }
+ }
+
+ public void assertResponseContains(String path, String needle) throws IOException
+ {
+ System.out.println("Issuing request to " + path);
+ String content = getResponse(path);
+ Assert.assertTrue("Content should contain <" + needle + ">, instead got <" + content + ">",content.contains(needle));
+ }
+
+ public void assertWebAppContextsExists(String... expectedContextPaths)
+ {
+ List contexts = getWebAppContexts();
+ if (expectedContextPaths.length != contexts.size())
+ {
+ System.out.println("## Expected Contexts");
+ for (String expected : expectedContextPaths)
+ {
+ System.out.println(expected);
+ }
+ System.out.println("## Actual Contexts");
+ for (WebAppContext context : contexts)
+ {
+ System.out.printf("%s ## %s%n",context.getContextPath(),context);
+ }
+ Assert.assertEquals("Contexts.size",expectedContextPaths.length,contexts.size());
+ }
+
+ for (String expectedPath : expectedContextPaths)
+ {
+ boolean found = false;
+ for (WebAppContext context : contexts)
+ {
+ if (context.getContextPath().equals(expectedPath))
+ {
+ found = true;
+ break;
+ }
+ }
+ Assert.assertTrue("Did not find Expected Context Path " + expectedPath,found);
+ }
+ }
+
+ public void copyContext(String srcName, String destName) throws IOException
+ {
+ System.out.printf("Copying Context: %s -> %s%n",srcName,destName);
+ File srcDir = MavenTestingUtils.getTestResourceDir("contexts");
+ File destDir = new File(jettyHome,"contexts");
+
+ File srcFile = new File(srcDir,srcName);
+ File destFile = new File(destDir,destName);
+
+ copyFile("Context",srcFile,destFile);
+ }
+
+ private void copyFile(String type, File srcFile, File destFile) throws IOException
+ {
+ PathAssert.assertFileExists(type + " File",srcFile);
+ IO.copyFile(srcFile,destFile);
+ PathAssert.assertFileExists(type + " File",destFile);
+ System.out.printf("Copy %s: %s%n To %s: %s%n",type,srcFile,type,destFile);
+ System.out.printf("Destination Exists: %s - %s%n",destFile.exists(),destFile);
+ }
+
+ public void copyWebapp(String srcName, String destName) throws IOException
+ {
+ System.out.printf("Copying Webapp: %s -> %s%n",srcName,destName);
+ File srcDir = MavenTestingUtils.getTestResourceDir("webapps");
+ File destDir = new File(jettyHome,"webapps");
+
+ File srcFile = new File(srcDir,srcName);
+ File destFile = new File(destDir,destName);
+
+ copyFile("Webapp",srcFile,destFile);
+ }
+
+ private void deleteContents(File dir)
+ {
+ System.out.printf("Delete (dir) %s/%n",dir);
+ for (File file : dir.listFiles())
+ {
+ // Safety measure. only recursively delete within target directory.
+ if (file.isDirectory() && file.getAbsolutePath().contains("target" + File.separator))
+ {
+ deleteContents(file);
+ Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
+ }
+ else
+ {
+ System.out.printf("Delete (file) %s%n",file);
+ Assert.assertTrue("Delete failed: " + file.getAbsolutePath(),file.delete());
+ }
+ }
+ }
+
+ public DeploymentManager getActiveDeploymentManager()
+ {
+ List depmans = server.getBeans(DeploymentManager.class);
+ Assert.assertEquals("DeploymentManager bean count",1,depmans.size());
+ return depmans.get(0);
+ }
+
+ public File getJettyDir(String name)
+ {
+ return new File(jettyHome,name);
+ }
+
+ public File getJettyHome()
+ {
+ return jettyHome;
+ }
+
+ public String getScheme()
+ {
+ return scheme;
+ }
+
+ public Server getServer()
+ {
+ return server;
+ }
+
+ public int getServerPort()
+ {
+ return serverPort;
+ }
+
+ public URI getServerURI() throws UnknownHostException
+ {
+ StringBuffer uri = new StringBuffer();
+ uri.append(this.scheme).append("://");
+ uri.append(InetAddress.getLocalHost().getHostAddress());
+ uri.append(":").append(this.serverPort);
+ return URI.create(uri.toString());
+ }
+
+ public List getWebAppContexts()
+ {
+ List contexts = new ArrayList();
+ HandlerCollection handlers = (HandlerCollection)server.getHandler();
+ System.out.println(server.dump());
+ Handler children[] = handlers.getChildHandlers();
+
+ for (Handler handler : children)
+ {
+ if (handler instanceof WebAppContext)
+ {
+ WebAppContext context = (WebAppContext)handler;
+ contexts.add(context);
+ }
+ }
+
+ return contexts;
+ }
+
+ @SuppressWarnings("unchecked")
+ public void load() throws Exception
+ {
+ XmlConfiguration last = null;
+ Object[] obj = new Object[this.xmlConfigurations.size()];
+
+ // Configure everything
+ for (int i = 0; i < this.xmlConfigurations.size(); i++)
+ {
+ URL configURL = this.xmlConfigurations.get(i);
+ XmlConfiguration configuration = new XmlConfiguration(configURL);
+ if (last != null)
+ {
+ configuration.getIdMap().putAll(last.getIdMap());
+ }
+ configuration.setProperties(properties);
+ obj[i] = configuration.configure();
+ last = configuration;
+ }
+
+ // Test for Server Instance.
+ Server foundServer = null;
+ int serverCount = 0;
+ for (int i = 0; i < this.xmlConfigurations.size(); i++)
+ {
+ if (obj[i] instanceof Server)
+ {
+ if (obj[i].equals(foundServer))
+ {
+ // Identical server instance found
+ break;
+ }
+ foundServer = (Server)obj[i];
+ serverCount++;
+ }
+ }
+
+ if (serverCount <= 0)
+ {
+ throw new Exception("Load failed to configure a " + Server.class.getName());
+ }
+
+ Assert.assertEquals("Server load count",1,serverCount);
+
+ this.server = foundServer;
+ this.server.setGracefulShutdown(10);
+
+ }
+
+ public void printHandlers(PrintStream out)
+ {
+ Handler handler = server.getHandler();
+ out.println(toString(handler));
+ }
+
+ public void removeContext(String name)
+ {
+ File destDir = new File(jettyHome,"contexts");
+ File contextFile = new File(destDir,name);
+ if (contextFile.exists())
+ {
+ Assert.assertTrue("Delete of Context file: " + contextFile.getAbsolutePath(),contextFile.delete());
+ }
+ }
+
+ public void setProperty(String key, String value)
+ {
+ properties.setProperty(key,value);
+ }
+
+ public void setScheme(String scheme)
+ {
+ this.scheme = scheme;
+ }
+
+ public void start() throws Exception
+ {
+ Assert.assertNotNull("Server should not be null (failed load?)",server);
+
+ server.start();
+
+ // Find the active server port.
+ this.serverPort = (-1);
+ Connector connectors[] = server.getConnectors();
+ for (int i = 0; i < connectors.length; i++)
+ {
+ Connector connector = connectors[i];
+ if (connector.getLocalPort() > 0)
+ {
+ this.serverPort = connector.getLocalPort();
+ break;
+ }
+ }
+
+ Assert.assertTrue("Server Port is between 1 and 65535. Actually <" + serverPort + ">",(1 <= this.serverPort) && (this.serverPort <= 65535));
+
+ // Uncomment to have server start and continue to run (without exiting)
+ // System.out.printf("Listening to port %d%n",this.serverPort);
+ // server.join();
+ }
+
+ public void stop() throws Exception
+ {
+ server.stop();
+ }
+
+ public String toString(Handler handler)
+ {
+ if (handler instanceof HandlerCollection)
+ {
+ return ((HandlerCollection)handler).dump();
+ }
+
+ if (handler instanceof AbstractHandler)
+ {
+ return ((AbstractHandler)handler).dump();
+ }
+
+ return handler.toString();
+ }
+
+ public void waitForDirectoryScan()
+ {
+ int ms = 2000;
+ System.out.printf("Waiting %d milliseconds for AppProvider to process directory scan ...%n",ms);
+ try
+ {
+ Thread.sleep(ms);
+ }
+ catch (InterruptedException ignore)
+ {
+ /* ignore */
+ }
+ }
+}
diff --git a/jetty-deploy/src/test/resources/contexts/foo.xml b/jetty-deploy/src/test/resources/contexts/foo.xml
new file mode 100644
index 00000000000..cb3e7ddbb94
--- /dev/null
+++ b/jetty-deploy/src/test/resources/contexts/foo.xml
@@ -0,0 +1,8 @@
+
+
+
+ /foo
+
+ /foo.war
+
+
diff --git a/jetty-deploy/src/test/resources/etc/realm.properties b/jetty-deploy/src/test/resources/etc/realm.properties
new file mode 100644
index 00000000000..cbf905de9fb
--- /dev/null
+++ b/jetty-deploy/src/test/resources/etc/realm.properties
@@ -0,0 +1,21 @@
+#
+# This file defines users passwords and roles for a HashUserRealm
+#
+# The format is
+# : [, ...]
+#
+# Passwords may be clear text, obfuscated or checksummed. The class
+# org.eclipse.util.Password should be used to generate obfuscated
+# passwords or password checksums
+#
+# If DIGEST Authentication is used, the password must be in a recoverable
+# format, either plain text or OBF:.
+#
+jetty: MD5:164c88b302622e17050af52c89945d44,user
+admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin
+other: OBF:1xmk1w261u9r1w1c1xmq,user
+plain: plain,user
+user: password,user
+
+# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password
+digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
diff --git a/jetty-deploy/src/test/resources/etc/webdefault.xml b/jetty-deploy/src/test/resources/etc/webdefault.xml
new file mode 100644
index 00000000000..35a5a9b7a10
--- /dev/null
+++ b/jetty-deploy/src/test/resources/etc/webdefault.xml
@@ -0,0 +1,404 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Default web.xml file.
+ This file is applied to a Web application before it's own WEB_INF/web.xml file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ default
+ org.eclipse.jetty.servlet.DefaultServlet
+
+ acceptRanges
+ true
+
+
+ dirAllowed
+ true
+
+
+ welcomeServlets
+ false
+
+
+ redirectWelcome
+ false
+
+
+ maxCacheSize
+ 256000000
+
+
+ maxCachedFileSize
+ 10000000
+
+
+ maxCachedFiles
+ 1000
+
+
+ cacheType
+ both
+
+
+ gzip
+ true
+
+
+ useFileMappedBuffer
+ true
+
+
+ 0
+
+
+ default/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ jsp
+ org.apache.jasper.servlet.JspServlet
+
+ logVerbosityLevel
+ DEBUG
+
+
+ fork
+ false
+
+
+ xpoweredBy
+ false
+
+
+ 0
+
+
+
+ jsp
+ *.jsp
+ *.jspf
+ *.jspx
+ *.xsp
+ *.JSP
+ *.JSPF
+ *.JSPX
+ *.XSP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 30
+
+
+
+
+
+
+
+
+
+
+
+
+ index.html
+ index.htm
+ index.jsp
+
+
+
+
+ arISO-8859-6
+ beISO-8859-5
+ bgISO-8859-5
+ caISO-8859-1
+ csISO-8859-2
+ daISO-8859-1
+ deISO-8859-1
+ elISO-8859-7
+ enISO-8859-1
+ esISO-8859-1
+ etISO-8859-1
+ fiISO-8859-1
+ frISO-8859-1
+ hrISO-8859-2
+ huISO-8859-2
+ isISO-8859-1
+ itISO-8859-1
+ iwISO-8859-8
+ jaShift_JIS
+ koEUC-KR
+ ltISO-8859-2
+ lvISO-8859-2
+ mkISO-8859-5
+ nlISO-8859-1
+ noISO-8859-1
+ plISO-8859-2
+ ptISO-8859-1
+ roISO-8859-2
+ ruISO-8859-5
+ shISO-8859-5
+ skISO-8859-2
+ slISO-8859-2
+ sqISO-8859-2
+ srISO-8859-5
+ svISO-8859-1
+ trISO-8859-9
+ ukISO-8859-5
+ zhGB2312
+ zh_TWBig5
+
+
+
+
+ Disable TRACE
+ /
+ TRACE
+
+
+
+
+
+
diff --git a/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml
new file mode 100644
index 00000000000..df27c6e13cf
--- /dev/null
+++ b/jetty-deploy/src/test/resources/jetty-deploymgr-contexts.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /xml-configured-jetty.properties
+
+
+
+
+
+
+
+
+
+ /contexts
+
+ 1
+ true
+
+
+
+
+
+
+
+
diff --git a/jetty-deploy/src/test/resources/jetty.xml b/jetty-deploy/src/test/resources/jetty.xml
new file mode 100644
index 00000000000..d0cb5017a6f
--- /dev/null
+++ b/jetty-deploy/src/test/resources/jetty.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+ 200
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 300000
+ 2
+ false
+ 8443
+ 20000
+ 5000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Test Realm
+ /etc/realm.properties
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /logs/yyyy_mm_dd.request.log
+ yyyy_MM_dd
+ 90
+ true
+ false
+ false
+ GMT
+
+
+
+
+
+
+
+ true
+ true
+ true
+ 1000
+
+
diff --git a/jetty-deploy/src/test/resources/webapps/foo-webapp-1.war b/jetty-deploy/src/test/resources/webapps/foo-webapp-1.war
new file mode 100644
index 00000000000..e4ed5bed1e1
Binary files /dev/null and b/jetty-deploy/src/test/resources/webapps/foo-webapp-1.war differ
diff --git a/jetty-deploy/src/test/resources/webapps/foo-webapp-2.war b/jetty-deploy/src/test/resources/webapps/foo-webapp-2.war
new file mode 100644
index 00000000000..df0f023812d
Binary files /dev/null and b/jetty-deploy/src/test/resources/webapps/foo-webapp-2.war differ
diff --git a/jetty-deploy/src/test/resources/webapps/foo-webapp-3.war b/jetty-deploy/src/test/resources/webapps/foo-webapp-3.war
new file mode 100644
index 00000000000..649d81bad5f
Binary files /dev/null and b/jetty-deploy/src/test/resources/webapps/foo-webapp-3.war differ
diff --git a/jetty-deploy/src/test/resources/webapps/logcommon.war b/jetty-deploy/src/test/resources/webapps/logcommon.war
new file mode 100644
index 00000000000..bac25eb2da6
Binary files /dev/null and b/jetty-deploy/src/test/resources/webapps/logcommon.war differ