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.plugins maven-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 + junit junit + 4.7 test + + + + 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: + *

+ * + *

    + *
  1. Tracking Apps and their LifeCycle Location
  2. + *
  3. Managing AppProviders and the Apps that they provide.
  4. + *
  5. Executing AppLifeCycle on App based on current and desired LifeCycle Location.
  6. + *
+ *

+ * + */ +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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + G + incoming_app + command_deploy + state_deployed + + + + + + + + Phase + DEPLOY + + + + + + State + UNAVAILABLE + + + + + + Phase + UNDEPLOY + + + + + Incoming WebApp + + + + + + + + State + DEPLOYED + + + + + + State + STAGED + + + + + + + + Phase + STAGE + + + + + + + Deployment + Lifecycle + + + + + Phase + START + + + + + + Phase + STOP + + + + + + + + State + STARTED + + + + + 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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + G + incoming_app + command_deploy + state_deployed + + + + + + Deployment + Management + + + + + DeploymentManager + + + + + + AppProvider + Contexts Dir + + + + + + AppProvider + Webapps Dir + + + + + + + + + + File System + + + + + + + + + DefaultDeployer + + + + + + DefaultStarter + + + + + + DefaultStopper + + + + + + DefaultUndeployer + + + + + + Jetty Server + + + + + Handler + + + + + + + + Create/Add ContextHandler + Start Handler + Stop Handler + Remove Handler + + Process Phases + + Lifecycle + + App + + StartStop + App + 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 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + G + incoming_app + command_deploy + state_deployed + + + + + + + + + Deployment + Manager + + + + + + Bind + Lifecycle + + + + + + Use + Lifecycle + + + + + Deployment + Lifecycle + + + + + App + Provider + + + + + + Manage + Provider + + + + + + Process + App + + + + + + + + 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