From 79861eaa05f897314079c1d5bd79f6f3ae6106a0 Mon Sep 17 00:00:00 2001
From: Jesse McConnell
Date: Tue, 28 Aug 2012 09:35:45 -0500
Subject: [PATCH] adding in jetty-maven-plugin
---
jetty-maven-plugin/.gitignore | 5 +
jetty-maven-plugin/pom.xml | 139 +++
.../jetty/maven/plugin/AbstractJettyMojo.java | 995 ++++++++++++++++++
.../jetty/maven/plugin/ConsoleScanner.java | 125 +++
.../jetty/maven/plugin/JettyDeployWar.java | 46 +
.../maven/plugin/JettyRunForkedMojo.java | 827 +++++++++++++++
.../jetty/maven/plugin/JettyRunMojo.java | 601 +++++++++++
.../maven/plugin/JettyRunWarExplodedMojo.java | 173 +++
.../jetty/maven/plugin/JettyRunWarMojo.java | 155 +++
.../jetty/maven/plugin/JettyServer.java | 128 +++
.../jetty/maven/plugin/JettyStartMojo.java | 40 +
.../jetty/maven/plugin/JettyStopMojo.java | 100 ++
.../maven/plugin/JettyWebAppContext.java | 394 +++++++
.../plugin/MavenWebInfConfiguration.java | 231 ++++
.../eclipse/jetty/maven/plugin/Monitor.java | 149 +++
.../eclipse/jetty/maven/plugin/PluginLog.java | 44 +
.../jetty/maven/plugin/ScanTargetPattern.java | 88 ++
.../eclipse/jetty/maven/plugin/Starter.java | 318 ++++++
.../jetty/maven/plugin/SystemProperties.java | 79 ++
.../jetty/maven/plugin/SystemProperty.java | 103 ++
20 files changed, 4740 insertions(+)
create mode 100644 jetty-maven-plugin/.gitignore
create mode 100644 jetty-maven-plugin/pom.xml
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ConsoleScanner.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java
create mode 100644 jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java
diff --git a/jetty-maven-plugin/.gitignore b/jetty-maven-plugin/.gitignore
new file mode 100644
index 00000000000..929d903c7d3
--- /dev/null
+++ b/jetty-maven-plugin/.gitignore
@@ -0,0 +1,5 @@
+.classpath
+.project
+.settings
+target
+*.swp
diff --git a/jetty-maven-plugin/pom.xml b/jetty-maven-plugin/pom.xml
new file mode 100644
index 00000000000..2b34a6e9846
--- /dev/null
+++ b/jetty-maven-plugin/pom.xml
@@ -0,0 +1,139 @@
+
+
+ org.eclipse.jetty
+ jetty-project
+ 9.0.0-SNAPSHOT
+
+ 4.0.0
+ jetty-maven-plugin
+ maven-plugin
+ Jetty :: Jetty Maven Plugin
+
+ 3.0.3
+ 2.9
+
+
+
+
+ maven-compiler-plugin
+
+
+ 1.5
+ false
+
+
+
+ maven-surefire-plugin
+
+ true
+
+
+
+ maven-plugin-plugin
+ ${pluginToolsVersion}
+
+
+ exec-plugin-doc
+ generate-sources
+
+ xdoc
+ helpmojo
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-webapp
+ ${project.version}
+
+
+ javax.servlet
+ servlet-api
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-artifact
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-core
+ ${mavenVersion}
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-tools-api
+ ${pluginToolsVersion}
+
+
+ org.eclipse.jetty
+ jetty-plus
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-jndi
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-jmx
+ ${project.version}
+
+
+ org.eclipse.jetty
+ jetty-annotations
+ ${project.version}
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-project-info-reports-plugin
+ 2.1
+
+ false
+
+
+
+
+ project-team
+ mailing-list
+ cim
+ issue-tracking
+ license
+ scm
+
+
+
+
+
+
+
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
new file mode 100644
index 00000000000..a891f10caac
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java
@@ -0,0 +1,995 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.util.FileUtils;
+import org.eclipse.jetty.security.LoginService;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Server;
+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.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+
+
+/**
+ * AbstractJettyMojo
+ *
+ *
+ */
+public abstract class AbstractJettyMojo extends AbstractMojo
+{
+
+
+ /**
+ * Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
+ * Use WITH CAUTION as you may wind up with duplicate jars/classes.
+ *
+ * @since jetty-7.5.2
+ * @parameter default-value="false"
+ */
+ protected boolean useProvidedScope;
+
+
+ /**
+ * List of goals that are NOT to be used
+ *
+ * @since jetty-7.5.2
+ * @parameter
+ */
+ protected String[] excludedGoals;
+
+
+
+ /**
+ * List of connectors to use. If none are configured
+ * then the default is a single SelectChannelConnector at port 8080. You can
+ * override this default port number by using the system property jetty.port
+ * on the command line, eg: mvn -Djetty.port=9999 jetty:run. Consider using instead
+ * the <jettyXml> element to specify external jetty xml config file.
+ *
+ * @parameter
+ */
+ protected Connector[] connectors;
+
+
+ /**
+ * List of other contexts to set up. Consider using instead
+ * the <jettyXml> element to specify external jetty xml config file.
+ * Optional.
+ *
+ *
+ * @parameter
+ */
+ protected ContextHandler[] contextHandlers;
+
+
+ /**
+ * List of security realms to set up. Consider using instead
+ * the <jettyXml> element to specify external jetty xml config file.
+ * Optional.
+ *
+ *
+ * @parameter
+ */
+ protected LoginService[] loginServices;
+
+
+
+ /**
+ * A RequestLog implementation to use for the webapp at runtime.
+ * Consider using instead the <jettyXml> element to specify external jetty xml config file.
+ * Optional.
+ *
+ *
+ * @parameter
+ */
+ protected RequestLog requestLog;
+
+
+
+ /**
+ * An instance of org.eclipse.jetty.webapp.WebAppContext that represents the webapp.
+ * Use any of its setters to configure the webapp. This is the preferred and most
+ * flexible method of configuration, rather than using the (deprecated) individual
+ * parameters like "tmpDirectory", "contextPath" etc.
+ *
+ * @parameter alias="webAppConfig"
+ */
+ protected JettyWebAppContext webApp;
+
+
+
+ /**
+ * The context path for the webapp. Defaults to the
+ * name of the webapp's artifact.
+ *
+ * @deprecated Use <webApp><contextPath> instead.
+ * @parameter expression="/${project.artifactId}"
+ * @required
+ * @readonly
+ */
+ protected String contextPath;
+
+
+ /**
+ * The temporary directory to use for the webapp.
+ * Defaults to target/tmp.
+ *
+ * @deprecated Use %lt;webApp><tempDirectory> instead.
+ * @parameter expression="${project.build.directory}/tmp"
+ * @required
+ * @readonly
+ */
+ protected File tmpDirectory;
+
+
+
+ /**
+ * The interval in seconds to scan the webapp for changes
+ * and restart the context if necessary. Ignored if reload
+ * is enabled. Disabled by default.
+ *
+ * @parameter expression="${jetty.scanIntervalSeconds}" default-value="0"
+ * @required
+ */
+ protected int scanIntervalSeconds;
+
+
+ /**
+ * reload can be set to either 'automatic' or 'manual'
+ *
+ * if 'manual' then the context can be reloaded by a linefeed in the console
+ * if 'automatic' then traditional reloading on changed files is enabled.
+ *
+ * @parameter expression="${jetty.reload}" default-value="automatic"
+ */
+ protected String reload;
+
+ /**
+ * File containing system properties to be set before execution
+ *
+ * Note that these properties will NOT override System properties
+ * that have been set on the command line, by the JVM, or directly
+ * in the POM via systemProperties. Optional.
+ *
+ * @parameter expression="${jetty.systemPropertiesFile}"
+ */
+ protected File systemPropertiesFile;
+
+ /**
+ * System properties to set before execution.
+ * Note that these properties will NOT override System properties
+ * that have been set on the command line or by the JVM. They WILL
+ * override System properties that have been set via systemPropertiesFile.
+ * Optional.
+ * @parameter
+ */
+ protected SystemProperties systemProperties;
+
+
+
+ /**
+ * Comma separated list of a jetty xml configuration files whose contents
+ * will be applied before any plugin configuration. Optional.
+ *
+ *
+ * @parameter alias="jettyConfig"
+ */
+ protected String jettyXml;
+
+
+ /**
+ * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort>
+ * -DSTOP.KEY=<stopKey> -jar start.jar --stop
+ *
+ * @parameter
+ */
+ protected int stopPort;
+
+ /**
+ * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey>
+ * -DSTOP.PORT=<stopPort> -jar start.jar --stop
+ *
+ * @parameter
+ */
+ protected String stopKey;
+
+ /**
+ *
+ * Determines whether or not the server blocks when started. The default
+ * behavior (daemon = false) will cause the server to pause other processes
+ * while it continues to handle web requests. This is useful when starting the
+ * server with the intent to work with it interactively.
+ *
+ * Often, it is desirable to let the server start and continue running subsequent
+ * processes in an automated build environment. This can be facilitated by setting
+ * daemon to true.
+ *
+ *
+ * @parameter expression="${jetty.daemon}" default-value="false"
+ */
+ protected boolean daemon;
+
+ /**
+ * Skip this mojo execution.
+ *
+ * @parameter expression="${jetty.skip}" default-value="false"
+ */
+ protected boolean skip;
+
+
+ /**
+ * Location of a context xml configuration file whose contents
+ * will be applied to the webapp AFTER anything in <webApp>.Optional.
+ *
+ *
+ * @parameter alias="webAppXml"
+ */
+ protected String contextXml;
+
+
+ /**
+ * The maven project.
+ *
+ * @parameter expression="${project}"
+ * @readonly
+ */
+ protected MavenProject project;
+
+
+ /**
+ * The artifacts for the project.
+ *
+ * @parameter expression="${project.artifacts}"
+ * @readonly
+ */
+ protected Set projectArtifacts;
+
+
+ /**
+ * @parameter expression="${mojoExecution}"
+ * @readonly
+ */
+ private org.apache.maven.plugin.MojoExecution execution;
+
+
+
+ /**
+ * The artifacts for the plugin itself.
+ *
+ * @parameter expression="${plugin.artifacts}"
+ * @readonly
+ */
+ private List pluginArtifacts;
+
+
+
+ /**
+ * A wrapper for the Server object
+ */
+ protected JettyServer server;
+
+ /**
+ * A scanner to check for changes to the webapp
+ */
+ protected Scanner scanner;
+
+ /**
+ * List of files and directories to scan
+ */
+ protected ArrayList scanList;
+
+ /**
+ * List of Listeners for the scanner
+ */
+ protected ArrayList scannerListeners;
+
+
+ /**
+ * A scanner to check ENTER hits on the console
+ */
+ protected Thread consoleScanner;
+
+
+ public String PORT_SYSPROPERTY = "jetty.port";
+
+
+ public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
+
+ public abstract void checkPomConfiguration() throws MojoExecutionException;
+
+ public abstract void configureScanner () throws MojoExecutionException;
+
+
+
+
+
+ public void execute() throws MojoExecutionException, MojoFailureException
+ {
+ getLog().info("Configuring Jetty for project: " + getProject().getName());
+ if (skip)
+ {
+ getLog().info("Skipping Jetty start: jetty.skip==true");
+ return;
+ }
+
+ if (isExcluded(execution.getMojoDescriptor().getGoal()))
+ {
+ getLog().info("The goal \""+execution.getMojoDescriptor().getFullGoalName()+
+ "\" has been made unavailable for this web application by an configuration.");
+ return;
+ }
+
+ configurePluginClasspath();
+ PluginLog.setLog(getLog());
+ checkPomConfiguration();
+ startJetty();
+ }
+
+
+ public void configurePluginClasspath() throws MojoExecutionException
+ {
+ //if we are configured to include the provided dependencies on the plugin's classpath
+ //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
+ //try and filter out ones that will clash with jars that are plugin dependencies, then
+ //create a new classloader that we setup in the parent chain.
+ if (useProvidedScope)
+ {
+ try
+ {
+ List provided = new ArrayList();
+ URL[] urls = null;
+
+ for ( Iterator iter = projectArtifacts.iterator(); iter.hasNext(); )
+ {
+ Artifact artifact = iter.next();
+ if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
+ {
+ provided.add(artifact.getFile().toURI().toURL());
+ if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
+ }
+ }
+
+ if (!provided.isEmpty())
+ {
+ urls = new URL[provided.size()];
+ provided.toArray(urls);
+ URLClassLoader loader = new URLClassLoader(urls, getClass().getClassLoader());
+ Thread.currentThread().setContextClassLoader(loader);
+ getLog().info("Plugin classpath augmented with provided dependencies: "+Arrays.toString(urls));
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ throw new MojoExecutionException("Invalid url", e);
+ }
+ }
+ }
+
+
+ public boolean isPluginArtifact(Artifact artifact)
+ {
+ if (pluginArtifacts == null || pluginArtifacts.isEmpty())
+ return false;
+
+ boolean isPluginArtifact = false;
+ for (Iterator iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
+ {
+ Artifact pluginArtifact = iter.next();
+ if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
+ if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
+ isPluginArtifact = true;
+ }
+
+ return isPluginArtifact;
+ }
+
+ public void finishConfigurationBeforeStart() throws Exception
+ {
+ HandlerCollection contexts = (HandlerCollection)server.getChildHandlerByClass(ContextHandlerCollection.class);
+ if (contexts==null)
+ contexts = (HandlerCollection)server.getChildHandlerByClass(HandlerCollection.class);
+
+ for (int i=0; (this.contextHandlers != null) && (i < this.contextHandlers.length); i++)
+ {
+ contexts.addHandler(this.contextHandlers[i]);
+ }
+ }
+
+
+
+
+ public void applyJettyXml() throws Exception
+ {
+ if (getJettyXmlFiles() == null)
+ return;
+
+ for ( File xmlFile : getJettyXmlFiles() )
+ {
+ getLog().info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );
+ XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
+ xmlConfiguration.configure(this.server);
+ }
+ }
+
+
+
+ public void startJetty () throws MojoExecutionException
+ {
+ try
+ {
+ getLog().debug("Starting Jetty Server ...");
+
+ printSystemProperties();
+ this.server = new JettyServer();
+ setServer(this.server);
+
+ //apply any config from a jetty.xml file first which is able to
+ //be overwritten by config in the pom.xml
+ applyJettyXml ();
+
+
+
+ // if the user hasn't configured their project's pom to use a
+ // different set of connectors,
+ // use the default
+ Connector[] connectors = this.server.getConnectors();
+ if (connectors == null|| connectors.length == 0)
+ {
+ //try using ones configured in pom
+ this.server.setConnectors(this.connectors);
+
+ connectors = this.server.getConnectors();
+ if (connectors == null || connectors.length == 0)
+ {
+ //if a SystemProperty -Djetty.port= has been supplied, use that as the default port
+ this.connectors = new Connector[] { this.server.createDefaultConnector(server, System.getProperty(PORT_SYSPROPERTY, null)) };
+ this.server.setConnectors(this.connectors);
+ }
+ }
+
+
+ //set up a RequestLog if one is provided
+ if (this.requestLog != null)
+ getServer().setRequestLog(this.requestLog);
+
+ //set up the webapp and any context provided
+ this.server.configureHandlers();
+ configureWebApplication();
+ this.server.addWebApplication(webApp);
+
+ // set up security realms
+ for (int i = 0; (this.loginServices != null) && i < this.loginServices.length; i++)
+ {
+ getLog().debug(this.loginServices[i].getClass().getName() + ": "+ this.loginServices[i].toString());
+ getServer().addBean(this.loginServices[i]);
+ }
+
+ //do any other configuration required by the
+ //particular Jetty version
+ finishConfigurationBeforeStart();
+
+ // start Jetty
+ this.server.start();
+
+ getLog().info("Started Jetty Server");
+
+ if(stopPort>0 && stopKey!=null)
+ {
+ Monitor monitor = new Monitor(stopPort, stopKey, new Server[]{server}, !daemon);
+ monitor.start();
+ }
+
+ // start the scanner thread (if necessary) on the main webapp
+ configureScanner ();
+ startScanner();
+
+ // start the new line scanner thread if necessary
+ startConsoleScanner();
+
+ // keep the thread going if not in daemon mode
+ if (!daemon )
+ {
+ server.join();
+ }
+ }
+ catch (Exception e)
+ {
+ throw new MojoExecutionException("Failure", e);
+ }
+ finally
+ {
+ if (!daemon )
+ {
+ getLog().info("Jetty server exiting.");
+ }
+ }
+
+ }
+
+
+
+ /**
+ * Subclasses should invoke this to setup basic info
+ * on the webapp
+ *
+ * @throws MojoExecutionException
+ */
+ public void configureWebApplication () throws Exception
+ {
+ //As of jetty-7, you must use a element
+ if (webApp == null)
+ webApp = new JettyWebAppContext();
+
+ //Apply any context xml file to set up the webapp
+ //CAUTION: if you've defined a element then the
+ //context xml file can OVERRIDE those settings
+ if (contextXml != null)
+ {
+ File file = FileUtils.getFile(contextXml);
+ XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(file));
+ getLog().info("Applying context xml file "+contextXml);
+ xmlConfiguration.configure(webApp);
+ }
+
+
+ //If no contextPath was specified, go with our default
+ String cp = webApp.getContextPath();
+ if (cp == null || "".equals(cp))
+ {
+ webApp.setContextPath((contextPath.startsWith("/") ? contextPath : "/"+ contextPath));
+ }
+
+ //If no tmp directory was specified, and we have one, use it
+ if (webApp.getTempDirectory() == null && tmpDirectory != null)
+ {
+ if (!tmpDirectory.exists())
+ tmpDirectory.mkdirs();
+
+ webApp.setTempDirectory(tmpDirectory);
+ }
+
+ getLog().info("Context path = " + webApp.getContextPath());
+ getLog().info("Tmp directory = "+ (webApp.getTempDirectory()== null? " determined at runtime": webApp.getTempDirectory()));
+ getLog().info("Web defaults = "+(webApp.getDefaultsDescriptor()==null?" jetty default":webApp.getDefaultsDescriptor()));
+ getLog().info("Web overrides = "+(webApp.getOverrideDescriptor()==null?" none":webApp.getOverrideDescriptor()));
+ }
+
+ /**
+ * Run a scanner thread on the given list of files and directories, calling
+ * stop/start on the given list of LifeCycle objects if any of the watched
+ * files change.
+ *
+ */
+ private void startScanner() throws Exception
+ {
+ // check if scanning is enabled
+ if (getScanIntervalSeconds() <= 0) return;
+
+ // check if reload is manual. It disables file scanning
+ if ( "manual".equalsIgnoreCase( reload ) )
+ {
+ // issue a warning if both scanIntervalSeconds and reload
+ // are enabled
+ getLog().warn("scanIntervalSeconds is set to " + scanIntervalSeconds + " but will be IGNORED due to manual reloading");
+ return;
+ }
+
+ scanner = new Scanner();
+ scanner.setReportExistingFilesOnStartup(false);
+ scanner.setScanInterval(getScanIntervalSeconds());
+ scanner.setScanDirs(getScanList());
+ scanner.setRecursive(true);
+ List listeners = getScannerListeners();
+ Iterator itor = (listeners==null?null:listeners.iterator());
+ while (itor!=null && itor.hasNext())
+ scanner.addListener((Scanner.Listener)itor.next());
+ getLog().info("Starting scanner at interval of " + getScanIntervalSeconds()+ " seconds.");
+ scanner.start();
+ }
+
+ /**
+ * Run a thread that monitors the console input to detect ENTER hits.
+ */
+ protected void startConsoleScanner() throws Exception
+ {
+ if ( "manual".equalsIgnoreCase( reload ) )
+ {
+ getLog().info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
+ consoleScanner = new ConsoleScanner(this);
+ consoleScanner.start();
+ }
+ }
+
+ private void printSystemProperties ()
+ {
+ // print out which system properties were set up
+ if (getLog().isDebugEnabled())
+ {
+ if (systemProperties != null)
+ {
+ Iterator itor = systemProperties.getSystemProperties().iterator();
+ while (itor.hasNext())
+ {
+ SystemProperty prop = (SystemProperty)itor.next();
+ getLog().debug("Property "+prop.getName()+"="+prop.getValue()+" was "+ (prop.isSet() ? "set" : "skipped"));
+ }
+ }
+ }
+ }
+
+ /**
+ * Try and find a jetty-web.xml file, using some
+ * historical naming conventions if necessary.
+ * @param webInfDir
+ * @return the jetty web xml file
+ */
+ public File findJettyWebXmlFile (File webInfDir)
+ {
+ if (webInfDir == null)
+ return null;
+ if (!webInfDir.exists())
+ return null;
+
+ File f = new File (webInfDir, "jetty-web.xml");
+ if (f.exists())
+ return f;
+
+ //try some historical alternatives
+ f = new File (webInfDir, "web-jetty.xml");
+ if (f.exists())
+ return f;
+
+ return null;
+ }
+
+
+ public Scanner getScanner ()
+ {
+ return scanner;
+ }
+
+ public MavenProject getProject()
+ {
+ return this.project;
+ }
+
+ public void setProject(MavenProject project) {
+ this.project = project;
+ }
+
+ public File getTmpDirectory()
+ {
+ return this.tmpDirectory;
+ }
+
+ public void setTmpDirectory(File tmpDirectory) {
+ this.tmpDirectory = tmpDirectory;
+ }
+
+ /**
+ * @return Returns the contextPath.
+ */
+ public String getContextPath()
+ {
+ return this.contextPath;
+ }
+
+ public void setContextPath(String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ /**
+ * @return Returns the scanIntervalSeconds.
+ */
+ public int getScanIntervalSeconds()
+ {
+ return this.scanIntervalSeconds;
+ }
+
+ public void setScanIntervalSeconds(int scanIntervalSeconds) {
+ this.scanIntervalSeconds = scanIntervalSeconds;
+ }
+
+ /**
+ * @return returns the path to the systemPropertiesFile
+ */
+ public File getSystemPropertiesFile()
+ {
+ return this.systemPropertiesFile;
+ }
+
+ public void setSystemPropertiesFile(File file) throws Exception
+ {
+ this.systemPropertiesFile = file;
+ FileInputStream propFile = new FileInputStream(systemPropertiesFile);
+ Properties properties = new Properties();
+ properties.load(propFile);
+
+ if (this.systemProperties == null )
+ this.systemProperties = new SystemProperties();
+
+ for (Enumeration keys = properties.keys(); keys.hasMoreElements(); )
+ {
+ String key = (String)keys.nextElement();
+ if ( ! systemProperties.containsSystemProperty(key) )
+ {
+ SystemProperty prop = new SystemProperty();
+ prop.setKey(key);
+ prop.setValue(properties.getProperty(key));
+
+ this.systemProperties.setSystemProperty(prop);
+ }
+ }
+
+ }
+
+ public void setSystemProperties(SystemProperties systemProperties)
+ {
+ if (this.systemProperties == null)
+ this.systemProperties = systemProperties;
+ else
+ {
+ Iterator itor = systemProperties.getSystemProperties().iterator();
+ while (itor.hasNext())
+ {
+ SystemProperty prop = (SystemProperty)itor.next();
+ this.systemProperties.setSystemProperty(prop);
+ }
+ }
+ }
+
+ public SystemProperties getSystemProperties ()
+ {
+ return this.systemProperties;
+ }
+
+ public List getJettyXmlFiles()
+ {
+ if ( this.jettyXml == null )
+ {
+ return null;
+ }
+
+ List jettyXmlFiles = new ArrayList();
+
+ if ( this.jettyXml.indexOf(',') == -1 )
+ {
+ jettyXmlFiles.add( new File( this.jettyXml ) );
+ }
+ else
+ {
+ String[] files = this.jettyXml.split(",");
+
+ for ( String file : files )
+ {
+ jettyXmlFiles.add( new File(file) );
+ }
+ }
+
+ return jettyXmlFiles;
+ }
+
+
+ public JettyServer getServer ()
+ {
+ return this.server;
+ }
+
+ public void setServer (JettyServer server)
+ {
+ this.server = server;
+ }
+
+
+ public void setScanList (ArrayList list)
+ {
+ this.scanList = new ArrayList(list);
+ }
+
+ public ArrayList getScanList ()
+ {
+ return this.scanList;
+ }
+
+
+ public void setScannerListeners (ArrayList listeners)
+ {
+ this.scannerListeners = new ArrayList(listeners);
+ }
+
+ public ArrayList getScannerListeners()
+ {
+ return this.scannerListeners;
+ }
+
+ public JettyWebAppContext getWebAppConfig()
+ {
+ return webApp;
+ }
+
+ public void setWebAppConfig(JettyWebAppContext webAppConfig)
+ {
+ this.webApp = webAppConfig;
+ }
+
+ public RequestLog getRequestLog()
+ {
+ return requestLog;
+ }
+
+ public void setRequestLog(RequestLog requestLog)
+ {
+ this.requestLog = requestLog;
+ }
+
+ public LoginService[] getLoginServices()
+ {
+ return loginServices;
+ }
+
+ public void setLoginServices(LoginService[] loginServices)
+ {
+ this.loginServices = loginServices;
+ }
+
+ public ContextHandler[] getContextHandlers()
+ {
+ return contextHandlers;
+ }
+
+ public void setContextHandlers(ContextHandler[] contextHandlers)
+ {
+ this.contextHandlers = contextHandlers;
+ }
+
+ public Connector[] getConnectors()
+ {
+ return connectors;
+ }
+
+ public void setConnectors(Connector[] connectors)
+ {
+ this.connectors = connectors;
+ }
+
+ public String getReload()
+ {
+ return reload;
+ }
+
+ public void setReload(String reload)
+ {
+ this.reload = reload;
+ }
+
+ public String getJettyConfig()
+ {
+ return jettyXml;
+ }
+
+ public void setJettyConfig(String jettyConfig)
+ {
+ this.jettyXml = jettyConfig;
+ }
+
+ public String getWebAppXml()
+ {
+ return contextXml;
+ }
+
+ public void setWebAppXml(String webAppXml)
+ {
+ this.contextXml = webAppXml;
+ }
+
+ public boolean isSkip()
+ {
+ return skip;
+ }
+
+ public void setSkip(boolean skip)
+ {
+ this.skip = skip;
+ }
+
+ public boolean isDaemon()
+ {
+ return daemon;
+ }
+
+ public void setDaemon(boolean daemon)
+ {
+ this.daemon = daemon;
+ }
+
+ public String getStopKey()
+ {
+ return stopKey;
+ }
+
+ public void setStopKey(String stopKey)
+ {
+ this.stopKey = stopKey;
+ }
+
+ public int getStopPort()
+ {
+ return stopPort;
+ }
+
+ public void setStopPort(int stopPort)
+ {
+ this.stopPort = stopPort;
+ }
+
+ public List getPluginArtifacts()
+ {
+ return pluginArtifacts;
+ }
+
+ public void setPluginArtifacts(List pluginArtifacts)
+ {
+ this.pluginArtifacts = pluginArtifacts;
+ }
+
+
+ public boolean isExcluded (String goal)
+ {
+ if (excludedGoals == null || goal == null)
+ return false;
+
+ goal = goal.trim();
+ if ("".equals(goal))
+ return false;
+
+ boolean excluded = false;
+ for (int i=0; i 0) {
+ int inputByte = System.in.read();
+ if (inputByte >= 0)
+ {
+ char c = (char)inputByte;
+ if (c == '\n') {
+ restartWebApp();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Skip buffered bytes of system console.
+ */
+ private void clearInputBuffer()
+ {
+ try
+ {
+ while (System.in.available() > 0)
+ {
+ // System.in.skip doesn't work properly. I don't know why
+ long available = System.in.available();
+ for (int i = 0; i < available; i++)
+ {
+ if (System.in.read() == -1)
+ {
+ break;
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ mojo.getLog().warn("Error discarding console input buffer", e);
+ }
+ }
+
+ private void restartWebApp()
+ {
+ try
+ {
+ mojo.restartWebApp(false);
+ // Clear input buffer to discard anything entered on the console
+ // while the application was being restarted.
+ clearInputBuffer();
+ }
+ catch (Exception e)
+ {
+ mojo.getLog().error(
+ "Error reconfiguring/restarting webapp after a new line on the console",
+ e);
+ }
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java
new file mode 100644
index 00000000000..5168a919d6e
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyDeployWar.java
@@ -0,0 +1,46 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+
+/**
+ *
+ * This goal is used to run Jetty with a pre-assembled war.
+ *
+ *
+ * It accepts exactly the same options as the run-war goal.
+ * However, it doesn't assume that the current artifact is a
+ * webapp and doesn't try to assemble it into a war before its execution.
+ * So using it makes sense only when used in conjunction with the
+ * webApp configuration parameter pointing to a pre-built WAR.
+ *
+ *
+ * This goal is useful e.g. for launching a web app in Jetty as a target for unit-tested
+ * HTTP client components.
+ *
+ *
+ * @goal deploy-war
+ * @requiresDependencyResolution runtime
+ * @execute phase="validate"
+ * @description Deploy a pre-assembled war
+ *
+ */
+public class JettyDeployWar extends JettyRunWarMojo
+{
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
new file mode 100644
index 00000000000..f37a67ff663
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java
@@ -0,0 +1,827 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.repository.ComponentDependency;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.resource.Resource;
+
+
+/**
+ *
+ * This goal is used to assemble your webapp into a war and automatically deploy it to Jetty in a forked JVM.
+ *
+ *
+ * You need to define a jetty.xml file to configure connectors etc and a context xml file that sets up anything special
+ * about your webapp. This plugin will fill in the:
+ *
+ *
context path
+ *
classes
+ *
web.xml
+ *
root of the webapp
+ *
+ * Based on a combination of information that you supply and the location of files in your unassembled webapp.
+ *
+ *
+ * There is a reference guide to the configuration parameters for this plugin, and more detailed information
+ * with examples in the Configuration Guide.
+ *
+ *
+ * @goal run-forked
+ * @requiresDependencyResolution compile+runtime
+ * @execute phase="test-compile"
+ * @description Runs Jetty in forked JVM on an unassembled webapp
+ *
+ */
+public class JettyRunForkedMojo extends AbstractMojo
+{
+ public String PORT_SYSPROPERTY = "jetty.port";
+
+ /**
+ * Whether or not to include dependencies on the plugin's classpath with <scope>provided</scope>
+ * Use WITH CAUTION as you may wind up with duplicate jars/classes.
+ * @parameter default-value="false"
+ */
+ protected boolean useProvidedScope;
+
+
+ /**
+ * The maven project.
+ *
+ * @parameter expression="${project}"
+ * @required
+ * @readonly
+ */
+ private MavenProject project;
+
+
+
+ /**
+ * If true, the <testOutputDirectory>
+ * and the dependencies of <scope>test<scope>
+ * will be put first on the runtime classpath.
+ * @parameter alias="useTestClasspath" default-value="false"
+ */
+ private boolean useTestScope;
+
+
+ /**
+ * The default location of the web.xml file. Will be used
+ * if <webAppConfig><descriptor> is not set.
+ *
+ * @parameter expression="${basedir}/src/main/webapp/WEB-INF/web.xml"
+ * @readonly
+ */
+ private String webXml;
+
+ /**
+ * The target directory
+ *
+ * @parameter expression="${project.build.directory}"
+ * @required
+ * @readonly
+ */
+ protected File target;
+
+
+ /**
+ * The temporary directory to use for the webapp.
+ * Defaults to target/tmp
+ *
+ * @parameter expression="${project.build.directory}/tmp"
+ * @required
+ * @readonly
+ */
+ protected File tmpDirectory;
+
+
+ /**
+ * The directory containing generated classes.
+ *
+ * @parameter expression="${project.build.outputDirectory}"
+ * @required
+ *
+ */
+ private File classesDirectory;
+
+
+
+ /**
+ * The directory containing generated test classes.
+ *
+ * @parameter expression="${project.build.testOutputDirectory}"
+ * @required
+ */
+ private File testClassesDirectory;
+
+ /**
+ * Root directory for all html/jsp etc files
+ *
+ * @parameter expression="${basedir}/src/main/webapp"
+ *
+ */
+ private File webAppSourceDirectory;
+
+
+ /**
+ * Directories that contain static resources
+ * for the webapp. Optional.
+ *
+ * @parameter
+ */
+ private File[] resourceBases;
+
+
+ /**
+ * If true, the webAppSourceDirectory will be first on the list of
+ * resources that form the resource base for the webapp. If false,
+ * it will be last.
+ *
+ * @parameter default-value="true"
+ */
+ private boolean baseAppFirst;
+
+
+ /**
+ * Location of jetty xml configuration files whose contents
+ * will be applied before any plugin configuration. Optional.
+ * @parameter
+ */
+ private String jettyXml;
+
+ /**
+ * The context path for the webapp. Defaults to the
+ * name of the webapp's artifact.
+ *
+ * @parameter expression="/${project.artifactId}"
+ * @required
+ * @readonly
+ */
+ private String contextPath;
+
+
+ /**
+ * Location of a context xml configuration file whose contents
+ * will be applied to the webapp AFTER anything in <webAppConfig>.Optional.
+ * @parameter
+ */
+ private String contextXml;
+
+
+ /**
+ * @parameter expression="${jetty.skip}" default-value="false"
+ */
+ private boolean skip;
+
+ /**
+ * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort>
+ * -DSTOP.KEY=<stopKey> -jar start.jar --stop
+ * @parameter
+ * @required
+ */
+ protected int stopPort;
+
+ /**
+ * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey>
+ * -DSTOP.PORT=<stopPort> -jar start.jar --stop
+ * @parameter
+ * @required
+ */
+ protected String stopKey;
+
+
+ /**
+ * Arbitrary jvm args to pass to the forked process
+ * @parameter
+ */
+ private String jvmArgs;
+
+
+
+ /**
+ * @parameter expression="${plugin.artifacts}"
+ * @readonly
+ */
+ private List pluginArtifacts;
+
+
+ /**
+ * @parameter expression="${plugin}"
+ * @readonly
+ */
+ private PluginDescriptor plugin;
+
+
+
+ /**
+ * @parameter expression="true" default-value="true"
+ */
+ private boolean waitForChild;
+
+
+ private Process forkedProcess;
+
+ private Random random;
+
+
+
+ public class ShutdownThread extends Thread
+ {
+ public ShutdownThread()
+ {
+ super("RunForkedShutdown");
+ }
+
+ public void run ()
+ {
+ if (forkedProcess != null && waitForChild)
+ {
+ forkedProcess.destroy();
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.maven.plugin.Mojo#execute()
+ */
+ public void execute() throws MojoExecutionException, MojoFailureException
+ {
+ getLog().info("Configuring Jetty for project: " + project.getName());
+ if (skip)
+ {
+ getLog().info("Skipping Jetty start: jetty.skip==true");
+ return;
+ }
+ PluginLog.setLog(getLog());
+ Runtime.getRuntime().addShutdownHook(new ShutdownThread());
+ random = new Random();
+ startJettyRunner();
+ }
+
+
+ public List getProvidedJars() throws MojoExecutionException
+ {
+ //if we are configured to include the provided dependencies on the plugin's classpath
+ //(which mimics being on jetty's classpath vs being on the webapp's classpath), we first
+ //try and filter out ones that will clash with jars that are plugin dependencies, then
+ //create a new classloader that we setup in the parent chain.
+ if (useProvidedScope)
+ {
+
+ List provided = new ArrayList();
+ for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
+ {
+ Artifact artifact = iter.next();
+ if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) && !isPluginArtifact(artifact))
+ {
+ provided.add(artifact.getFile().getAbsolutePath());
+ if (getLog().isDebugEnabled()) { getLog().debug("Adding provided artifact: "+artifact);}
+ }
+ }
+ return provided;
+
+ }
+ else
+ return null;
+ }
+
+ /* ------------------------------------------------------------ */
+ public File prepareConfiguration() throws MojoExecutionException
+ {
+ try
+ {
+ //work out the configuration based on what is configured in the pom
+ File propsFile = new File (target, "fork.props");
+ if (propsFile.exists())
+ propsFile.delete();
+
+ propsFile.createNewFile();
+ //propsFile.deleteOnExit();
+
+ Properties props = new Properties();
+
+
+ //web.xml
+ if (webXml != null)
+ props.put("web.xml", webXml);
+
+ //sort out the context path
+ if (contextPath != null)
+ props.put("context.path", contextPath);
+
+ //sort out the tmp directory (make it if it doesn't exist)
+ if (tmpDirectory != null)
+ {
+ if (!tmpDirectory.exists())
+ tmpDirectory.mkdirs();
+ props.put("tmp.dir", tmpDirectory.getAbsolutePath());
+ }
+
+ //sort out base dir of webapp
+ if (webAppSourceDirectory != null)
+ props.put("base.dir", webAppSourceDirectory.getAbsolutePath());
+
+ //sort out the resource base directories of the webapp
+ StringBuilder builder = new StringBuilder();
+ if (baseAppFirst)
+ {
+ add((webAppSourceDirectory==null?null:webAppSourceDirectory.getAbsolutePath()), builder);
+ if (resourceBases != null)
+ {
+ for (File resDir:resourceBases)
+ add(resDir.getAbsolutePath(), builder);
+ }
+ }
+ else
+ {
+ if (resourceBases != null)
+ {
+ for (File resDir:resourceBases)
+ add(resDir.getAbsolutePath(), builder);
+ }
+ add((webAppSourceDirectory==null?null:webAppSourceDirectory.getAbsolutePath()), builder);
+ }
+ props.put("res.dirs", builder.toString());
+
+
+ //web-inf classes
+ List classDirs = getClassesDirs();
+ StringBuffer strbuff = new StringBuffer();
+ for (int i=0; i deps = getDependencyFiles();
+ strbuff.setLength(0);
+ for (int i=0; i overlays = getOverlays();
+ strbuff.setLength(0);
+ for (int i=0; i 0)
+ builder.append(",");
+ builder.append(string);
+ }
+
+ private List getClassesDirs ()
+ {
+ List classesDirs = new ArrayList();
+
+ //if using the test classes, make sure they are first
+ //on the list
+ if (useTestScope && (testClassesDirectory != null))
+ classesDirs.add(testClassesDirectory);
+
+ if (classesDirectory != null)
+ classesDirs.add(classesDirectory);
+
+ return classesDirs;
+ }
+
+
+
+ private List getOverlays()
+ throws MalformedURLException, IOException
+ {
+ List overlays = new ArrayList();
+ for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
+ {
+ Artifact artifact = (Artifact) iter.next();
+
+ if (artifact.getType().equals("war"))
+ overlays.add(artifact.getFile());
+ }
+
+ return overlays;
+ }
+
+
+
+ private List getDependencyFiles ()
+ {
+ List dependencyFiles = new ArrayList();
+
+ for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); )
+ {
+ Artifact artifact = (Artifact) iter.next();
+
+ if (((!Artifact.SCOPE_PROVIDED.equals(artifact.getScope())) && (!Artifact.SCOPE_TEST.equals( artifact.getScope())))
+ ||
+ (useTestScope && Artifact.SCOPE_TEST.equals( artifact.getScope())))
+ {
+ dependencyFiles.add(artifact.getFile());
+ getLog().debug( "Adding artifact " + artifact.getFile().getName() + " for WEB-INF/lib " );
+ }
+ }
+
+ return dependencyFiles;
+ }
+
+ public boolean isPluginArtifact(Artifact artifact)
+ {
+ if (pluginArtifacts == null || pluginArtifacts.isEmpty())
+ return false;
+
+ boolean isPluginArtifact = false;
+ for (Iterator iter = pluginArtifacts.iterator(); iter.hasNext() && !isPluginArtifact; )
+ {
+ Artifact pluginArtifact = iter.next();
+ if (getLog().isDebugEnabled()) { getLog().debug("Checking "+pluginArtifact);}
+ if (pluginArtifact.getGroupId().equals(artifact.getGroupId()) && pluginArtifact.getArtifactId().equals(artifact.getArtifactId()))
+ isPluginArtifact = true;
+ }
+
+ return isPluginArtifact;
+ }
+
+
+
+ private Set getExtraJars()
+ throws Exception
+ {
+ Set extraJars = new HashSet();
+
+
+ List l = pluginArtifacts;
+ Artifact pluginArtifact = null;
+
+ if (l != null)
+ {
+ Iterator itor = l.iterator();
+ while (itor.hasNext() && pluginArtifact == null)
+ {
+ Artifact a = (Artifact)itor.next();
+ if (a.getArtifactId().equals(plugin.getArtifactId())) //get the jetty-maven-plugin jar
+ {
+ extraJars.add(a);
+ }
+ }
+ }
+
+ return extraJars;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ public void startJettyRunner() throws MojoExecutionException
+ {
+ try
+ {
+
+ File props = prepareConfiguration();
+
+ List cmd = new ArrayList();
+ cmd.add(getJavaBin());
+
+ if (jvmArgs != null)
+ {
+ String[] args = jvmArgs.split(" ");
+ for (int i=0;args != null && i 0)
+ {
+ cmd.add("-cp");
+ cmd.add(classPath);
+ }
+ cmd.add(Starter.class.getCanonicalName());
+
+ if (stopPort > 0 && stopKey != null)
+ {
+ cmd.add("--stop-port");
+ cmd.add(Integer.toString(stopPort));
+ cmd.add("--stop-key");
+ cmd.add(stopKey);
+ }
+ if (jettyXml != null)
+ {
+ cmd.add("--jetty-xml");
+ cmd.add(jettyXml);
+ }
+
+ if (contextXml != null)
+ {
+ cmd.add("--context-xml");
+ cmd.add(contextXml);
+ }
+
+ cmd.add("--props");
+ cmd.add(props.getAbsolutePath());
+
+ String token = createToken();
+ cmd.add("--token");
+ cmd.add(token);
+
+ ProcessBuilder builder = new ProcessBuilder(cmd);
+ builder.directory(project.getBasedir());
+
+ if (PluginLog.getLog().isDebugEnabled())
+ PluginLog.getLog().debug(Arrays.toString(cmd.toArray()));
+
+ forkedProcess = builder.start();
+ PluginLog.getLog().info("Forked process starting");
+
+ if (waitForChild)
+ {
+ startPump("STDOUT",forkedProcess.getInputStream());
+ startPump("STDERR",forkedProcess.getErrorStream());
+ int exitcode = forkedProcess.waitFor();
+ PluginLog.getLog().info("Forked execution exit: "+exitcode);
+ }
+ else
+ {
+ //wait for the child to be ready before terminating.
+ //child indicates it has finished starting by printing on stdout the token passed to it
+ try
+ {
+ LineNumberReader reader = new LineNumberReader(new InputStreamReader(forkedProcess.getInputStream()));
+ String line = null;
+ int attempts = 10; //max lines we'll read trying to get token
+ do
+ {
+ --attempts;
+ line = reader.readLine();
+ if (line != null && line.startsWith(token))
+ break;
+ }
+ while (line != null && attempts>0);
+ reader.close();
+
+ if (line != null && line.trim().equals(token))
+ PluginLog.getLog().info("Forked process started.");
+ else
+ {
+ String err = (line==null?"":line.substring(token.length()));
+ PluginLog.getLog().info("Forked process startup errors "+(!"".equals(err)?":"+err:""));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new MojoExecutionException ("Problem determining if forked process is ready: "+e.getMessage());
+ }
+ }
+ }
+ catch (InterruptedException ex)
+ {
+ if (forkedProcess != null && waitForChild)
+ forkedProcess.destroy();
+
+ throw new MojoExecutionException("Failed to start Jetty within time limit");
+ }
+ catch (Exception ex)
+ {
+ if (forkedProcess != null && waitForChild)
+ forkedProcess.destroy();
+
+ throw new MojoExecutionException("Failed to create Jetty process", ex);
+ }
+ }
+
+
+
+ public String getClassPath() throws Exception
+ {
+ StringBuilder classPath = new StringBuilder();
+ for (Object obj : pluginArtifacts)
+ {
+ Artifact artifact = (Artifact) obj;
+ if ("jar".equals(artifact.getType()))
+ {
+ if (classPath.length() > 0)
+ {
+ classPath.append(File.pathSeparator);
+ }
+ classPath.append(artifact.getFile().getAbsolutePath());
+
+ }
+ }
+
+ //Any jars that we need from the plugin environment (like the ones containing Starter class)
+ Set extraJars = getExtraJars();
+ for (Artifact a:extraJars)
+ {
+ classPath.append(File.pathSeparator);
+ classPath.append(a.getFile().getAbsolutePath());
+ }
+
+
+ //Any jars that we need from the project's dependencies because we're useProvided
+ List providedJars = getProvidedJars();
+ if (providedJars != null && !providedJars.isEmpty())
+ {
+ for (String jar:providedJars)
+ {
+ classPath.append(File.pathSeparator);
+ classPath.append(jar);
+ if (getLog().isDebugEnabled()) getLog().debug("Adding provided jar: "+jar);
+ }
+ }
+
+ return classPath.toString();
+ }
+
+ private String getJavaBin()
+ {
+ String javaexes[] = new String[]
+ { "java", "java.exe" };
+
+ File javaHomeDir = new File(System.getProperty("java.home"));
+ for (String javaexe : javaexes)
+ {
+ File javabin = new File(javaHomeDir,fileSeparators("bin/" + javaexe));
+ if (javabin.exists() && javabin.isFile())
+ {
+ return javabin.getAbsolutePath();
+ }
+ }
+
+ return "java";
+ }
+
+ public static String fileSeparators(String path)
+ {
+ StringBuilder ret = new StringBuilder();
+ for (char c : path.toCharArray())
+ {
+ if ((c == '/') || (c == '\\'))
+ {
+ ret.append(File.separatorChar);
+ }
+ else
+ {
+ ret.append(c);
+ }
+ }
+ return ret.toString();
+ }
+
+ public static String pathSeparators(String path)
+ {
+ StringBuilder ret = new StringBuilder();
+ for (char c : path.toCharArray())
+ {
+ if ((c == ',') || (c == ':'))
+ {
+ ret.append(File.pathSeparatorChar);
+ }
+ else
+ {
+ ret.append(c);
+ }
+ }
+ return ret.toString();
+ }
+
+
+ private String createToken ()
+ {
+ return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase();
+ }
+
+
+ private void startPump(String mode, InputStream inputStream)
+ {
+ ConsoleStreamer pump = new ConsoleStreamer(mode,inputStream);
+ Thread thread = new Thread(pump,"ConsoleStreamer/" + mode);
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+
+
+
+ /**
+ * Simple streamer for the console output from a Process
+ */
+ private static class ConsoleStreamer implements Runnable
+ {
+ private String mode;
+ private BufferedReader reader;
+
+ public ConsoleStreamer(String mode, InputStream is)
+ {
+ this.mode = mode;
+ this.reader = new BufferedReader(new InputStreamReader(is));
+ }
+
+
+ public void run()
+ {
+ String line;
+ try
+ {
+ while ((line = reader.readLine()) != (null))
+ {
+ System.out.println("[" + mode + "] " + line);
+ }
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ finally
+ {
+ IO.close(reader);
+ }
+ }
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
new file mode 100644
index 00000000000..acd37629849
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
@@ -0,0 +1,601 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.codehaus.plexus.util.FileUtils;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ *
+ * This goal is used in-situ on a Maven project without first requiring that the project
+ * is assembled into a war, saving time during the development cycle.
+ * The plugin forks a parallel lifecycle to ensure that the "compile" phase has been completed before invoking Jetty. This means
+ * that you do not need to explicity execute a "mvn compile" first. It also means that a "mvn clean jetty:run" will ensure that
+ * a full fresh compile is done before invoking Jetty.
+ *
+ *
+ * Once invoked, the plugin can be configured to run continuously, scanning for changes in the project and automatically performing a
+ * hot redeploy when necessary. This allows the developer to concentrate on coding changes to the project using their IDE of choice and have those changes
+ * immediately and transparently reflected in the running web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying.
+ *
+ *
+ * You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ * This can be used, for example, to deploy a static webapp that is not part of your maven build.
+ *
+ *
+ * There is a reference guide to the configuration parameters for this plugin, and more detailed information
+ * with examples in the Configuration Guide.
+ *
+ *
+ *
+ * @goal run
+ * @requiresDependencyResolution test
+ * @execute phase="test-compile"
+ * @description Runs jetty directly from a maven project
+ */
+public class JettyRunMojo extends AbstractJettyMojo
+{
+ /**
+ * If true, the <testOutputDirectory>
+ * and the dependencies of <scope>test<scope>
+ * will be put first on the runtime classpath.
+ *
+ * @parameter alias="useTestClasspath" default-value="false"
+ */
+ private boolean useTestScope;
+
+
+ /**
+ * The default location of the web.xml file. Will be used
+ * if <webApp><descriptor> is not set.
+ *
+ * @parameter expression="${maven.war.webxml}"
+ * @readonly
+ */
+ private String webXml;
+
+
+ /**
+ * The directory containing generated classes.
+ *
+ * @parameter expression="${project.build.outputDirectory}"
+ * @required
+ *
+ */
+ private File classesDirectory;
+
+
+
+ /**
+ * The directory containing generated test classes.
+ *
+ * @parameter expression="${project.build.testOutputDirectory}"
+ * @required
+ */
+ private File testClassesDirectory;
+
+ /**
+ * Root directory for all html/jsp etc files
+ *
+ * @parameter expression="${maven.war.src}"
+ *
+ */
+ private File webAppSourceDirectory;
+
+
+ /**
+ * List of files or directories to additionally periodically scan for changes. Optional.
+ * @parameter
+ */
+ private File[] scanTargets;
+
+
+ /**
+ * List of directories with ant-style <include> and <exclude> patterns
+ * for extra targets to periodically scan for changes. Can be used instead of,
+ * or in conjunction with <scanTargets>.Optional.
+ * @parameter
+ */
+ private ScanTargetPattern[] scanTargetPatterns;
+
+
+
+
+ /**
+ * Extra scan targets as a list
+ */
+ private List extraScanTargets;
+
+
+
+ /**
+ * Verify the configuration given in the pom.
+ *
+ * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ */
+ public void checkPomConfiguration () throws MojoExecutionException
+ {
+ // check the location of the static content/jsps etc
+ try
+ {
+ if ((getWebAppSourceDirectory() == null) || !getWebAppSourceDirectory().exists())
+ {
+ webAppSourceDirectory = new File (project.getBasedir(), "src"+File.separator+"main"+File.separator+"webapp");
+ getLog().info("webAppSourceDirectory "+getWebAppSourceDirectory() +" does not exist. Defaulting to "+webAppSourceDirectory.getAbsolutePath());
+ }
+ else
+ getLog().info( "Webapp source directory = " + getWebAppSourceDirectory().getCanonicalPath());
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Webapp source directory does not exist", e);
+ }
+
+ // check reload mechanic
+ if ( !"automatic".equalsIgnoreCase( reload ) && !"manual".equalsIgnoreCase( reload ) )
+ {
+ throw new MojoExecutionException( "invalid reload mechanic specified, must be 'automatic' or 'manual'" );
+ }
+ else
+ {
+ getLog().info("Reload Mechanic: " + reload );
+ }
+
+
+ // check the classes to form a classpath with
+ try
+ {
+ //allow a webapp with no classes in it (just jsps/html)
+ if (getClassesDirectory() != null)
+ {
+ if (!getClassesDirectory().exists())
+ getLog().info( "Classes directory "+ getClassesDirectory().getCanonicalPath()+ " does not exist");
+ else
+ getLog().info("Classes = " + getClassesDirectory().getCanonicalPath());
+ }
+ else
+ getLog().info("Classes directory not set");
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Location of classesDirectory does not exist");
+ }
+
+
+ setExtraScanTargets(new ArrayList());
+ if (scanTargets != null)
+ {
+ for (int i=0; i< scanTargets.length; i++)
+ {
+ getLog().info("Added extra scan target:"+ scanTargets[i]);
+ getExtraScanTargets().add(scanTargets[i]);
+ }
+ }
+
+
+ if (scanTargetPatterns!=null)
+ {
+ for (int i=0;i files = FileUtils.getFiles(scanTargetPatterns[i].getDirectory(), includes, excludes);
+ itor = files.iterator();
+ while (itor.hasNext())
+ getLog().info("Adding extra scan target from pattern: "+itor.next());
+ List currentTargets = getExtraScanTargets();
+ if(currentTargets!=null && !currentTargets.isEmpty())
+ currentTargets.addAll(files);
+ else
+ setExtraScanTargets(files);
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException(e.getMessage());
+ }
+ }
+ }
+ }
+
+
+
+
+
+ public void configureWebApplication() throws Exception
+ {
+ super.configureWebApplication();
+
+ //Set up the location of the webapp.
+ //There are 2 parts to this: setWar() and setBaseResource(). On standalone jetty,
+ //the former could be the location of a packed war, while the latter is the location
+ //after any unpacking. With this mojo, you are running an unpacked, unassembled webapp,
+ //so the two locations should be equal.
+ Resource webAppSourceDirectoryResource = Resource.newResource(webAppSourceDirectory.getCanonicalPath());
+ if (webApp.getWar() == null)
+ webApp.setWar(webAppSourceDirectoryResource.toString());
+
+ if (webApp.getBaseResource() == null)
+ webApp.setBaseResource(webAppSourceDirectoryResource);
+
+ if (getClassesDirectory() != null)
+ webApp.setClasses (getClassesDirectory());
+ if (useTestScope && (testClassesDirectory != null))
+ webApp.setTestClasses (testClassesDirectory);
+ webApp.setWebInfLib (getDependencyFiles());
+
+
+ //if we have not already set web.xml location, need to set one up
+ if (webApp.getDescriptor() == null)
+ {
+ //Has an explicit web.xml file been configured to use?
+ if (webXml != null)
+ {
+ Resource r = Resource.newResource(webXml);
+ if (r.exists() && !r.isDirectory())
+ {
+ webApp.setDescriptor(r.toString());
+ }
+ }
+
+ //Still don't have a web.xml file: try the resourceBase of the webapp, if it is set
+ if (webApp.getDescriptor() == null && webApp.getBaseResource() != null)
+ {
+ Resource r = webApp.getBaseResource().addPath("WEB-INF/web.xml");
+ if (r.exists() && !r.isDirectory())
+ {
+ webApp.setDescriptor(r.toString());
+ }
+ }
+
+ //Still don't have a web.xml file: finally try the configured static resource directory if there is one
+ if (webApp.getDescriptor() == null && (webAppSourceDirectory != null))
+ {
+ File f = new File (new File (webAppSourceDirectory, "WEB-INF"), "web.xml");
+ if (f.exists() && f.isFile())
+ {
+ webApp.setDescriptor(f.getCanonicalPath());
+ }
+ }
+ }
+ getLog().info( "web.xml file = "+webApp.getDescriptor());
+ getLog().info("Webapp directory = " + getWebAppSourceDirectory().getCanonicalPath());
+ }
+
+ public void configureScanner ()
+ throws MojoExecutionException
+ {
+ // start the scanner thread (if necessary) on the main webapp
+ final ArrayList scanList = new ArrayList();
+ if (webApp.getDescriptor() != null)
+ {
+ try
+ {
+ Resource r = Resource.newResource(webApp.getDescriptor());
+ scanList.add(r.getFile());
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Problem configuring scanner for web.xml", e);
+ }
+ }
+
+ if (webApp.getJettyEnvXml() != null)
+ {
+ try
+ {
+ Resource r = Resource.newResource(webApp.getJettyEnvXml());
+ scanList.add(r.getFile());
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Problem configuring scanner for jetty-env.xml", e);
+ }
+ }
+
+ if (webApp.getDefaultsDescriptor() != null)
+ {
+ try
+ {
+ if (!WebAppContext.WEB_DEFAULTS_XML.equals(webApp.getDefaultsDescriptor()))
+ {
+ Resource r = Resource.newResource(webApp.getDefaultsDescriptor());
+ scanList.add(r.getFile());
+ }
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Problem configuring scanner for webdefaults.xml", e);
+ }
+ }
+
+ if (webApp.getOverrideDescriptor() != null)
+ {
+ try
+ {
+ Resource r = Resource.newResource(webApp.getOverrideDescriptor());
+ scanList.add(r.getFile());
+ }
+ catch (IOException e)
+ {
+ throw new MojoExecutionException("Problem configuring scanner for webdefaults.xml", e);
+ }
+ }
+
+
+ File jettyWebXmlFile = findJettyWebXmlFile(new File(getWebAppSourceDirectory(),"WEB-INF"));
+ if (jettyWebXmlFile != null)
+ scanList.add(jettyWebXmlFile);
+ scanList.addAll(getExtraScanTargets());
+ scanList.add(getProject().getFile());
+ if (webApp.getTestClasses() != null)
+ scanList.add(webApp.getTestClasses());
+ if (webApp.getClasses() != null)
+ scanList.add(webApp.getClasses());
+ scanList.addAll(webApp.getWebInfLib());
+ setScanList(scanList);
+ ArrayList listeners = new ArrayList();
+ listeners.add(new Scanner.BulkListener()
+ {
+ public void filesChanged (List changes)
+ {
+ try
+ {
+ boolean reconfigure = changes.contains(getProject().getFile().getCanonicalPath());
+ restartWebApp(reconfigure);
+ }
+ catch (Exception e)
+ {
+ getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+ }
+ }
+ });
+ setScannerListeners(listeners);
+ }
+
+ public void restartWebApp(boolean reconfigureScanner) throws Exception
+ {
+ getLog().info("restarting "+webApp);
+ getLog().debug("Stopping webapp ...");
+ webApp.stop();
+ getLog().debug("Reconfiguring webapp ...");
+
+ checkPomConfiguration();
+ configureWebApplication();
+
+ // check if we need to reconfigure the scanner,
+ // which is if the pom changes
+ if (reconfigureScanner)
+ {
+ getLog().info("Reconfiguring scanner after change to pom.xml ...");
+ scanList.clear();
+ scanList.add(new File(webApp.getDescriptor()));
+ if (webApp.getJettyEnvXml() != null)
+ scanList.add(new File(webApp.getJettyEnvXml()));
+ scanList.addAll(getExtraScanTargets());
+ scanList.add(getProject().getFile());
+ if (webApp.getTestClasses() != null)
+ scanList.add(webApp.getTestClasses());
+ if (webApp.getClasses() != null)
+ scanList.add(webApp.getClasses());
+ scanList.addAll(webApp.getWebInfLib());
+ getScanner().setScanDirs(scanList);
+ }
+
+ getLog().debug("Restarting webapp ...");
+ webApp.start();
+ getLog().info("Restart completed at "+new Date().toString());
+ }
+
+ private List getDependencyFiles ()
+ {
+ List dependencyFiles = new ArrayList();
+ List overlays = new ArrayList();
+ for ( Iterator iter = projectArtifacts.iterator(); iter.hasNext(); )
+ {
+ Artifact artifact = (Artifact) iter.next();
+ // Include runtime and compile time libraries, and possibly test libs too
+ if(artifact.getType().equals("war"))
+ {
+ try
+ {
+ Resource r=Resource.newResource("jar:"+Resource.toURL(artifact.getFile()).toString()+"!/");
+ overlays.add(r);
+ getExtraScanTargets().add(artifact.getFile());
+ }
+ catch(Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ continue;
+ }
+
+ if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()))
+ continue; //never add dependencies of scope=provided to the webapp's classpath (see also param)
+
+ if (Artifact.SCOPE_TEST.equals(artifact.getScope()) && !useTestScope)
+ continue; //only add dependencies of scope=test if explicitly required
+
+ dependencyFiles.add(artifact.getFile());
+ getLog().debug( "Adding artifact " + artifact.getFile().getName() + " with scope "+artifact.getScope()+" for WEB-INF/lib " );
+ }
+
+ webApp.setOverlays(overlays);
+
+ return dependencyFiles;
+ }
+
+
+
+
+ private List setUpClassPath(File webInfClasses, File testClasses, List webInfJars)
+ {
+ List classPathFiles = new ArrayList();
+ if (webInfClasses != null)
+ classPathFiles.add(webInfClasses);
+ if (testClasses != null)
+ classPathFiles.add(testClasses);
+ classPathFiles.addAll(webInfJars);
+
+ if (getLog().isDebugEnabled())
+ {
+ for (int i = 0; i < classPathFiles.size(); i++)
+ {
+ getLog().debug("classpath element: "+ ((File) classPathFiles.get(i)).getName());
+ }
+ }
+ return classPathFiles;
+ }
+
+ private List getClassesDirs ()
+ {
+ List classesDirs = new ArrayList();
+
+ //if using the test classes, make sure they are first
+ //on the list
+ if (useTestScope && (testClassesDirectory != null))
+ classesDirs.add(testClassesDirectory);
+
+ if (getClassesDirectory() != null)
+ classesDirs.add(getClassesDirectory());
+
+ return classesDirs;
+ }
+
+
+
+
+
+ public void execute() throws MojoExecutionException, MojoFailureException
+ {
+
+ super.execute();
+ }
+
+
+ public String getWebXml()
+ {
+ return this.webXml;
+ }
+
+ public void setWebXml(String webXml) {
+ this.webXml = webXml;
+ }
+
+ public File getClassesDirectory()
+ {
+ return this.classesDirectory;
+ }
+
+ public void setClassesDirectory(File classesDirectory) {
+ this.classesDirectory = classesDirectory;
+ }
+
+ public File getWebAppSourceDirectory()
+ {
+ return this.webAppSourceDirectory;
+ }
+
+ public void setWebAppSourceDirectory(File webAppSourceDirectory)
+ {
+ this.webAppSourceDirectory = webAppSourceDirectory;
+ }
+
+ public List getExtraScanTargets()
+ {
+ return this.extraScanTargets;
+ }
+
+ public void setExtraScanTargets(List list)
+ {
+ this.extraScanTargets = list;
+ }
+
+ public boolean isUseTestClasspath()
+ {
+ return useTestScope;
+ }
+
+ public void setUseTestClasspath(boolean useTestClasspath)
+ {
+ this.useTestScope = useTestClasspath;
+ }
+
+ public File getTestClassesDirectory()
+ {
+ return testClassesDirectory;
+ }
+
+ public void setTestClassesDirectory(File testClassesDirectory)
+ {
+ this.testClassesDirectory = testClassesDirectory;
+ }
+
+ public File[] getScanTargets()
+ {
+ return scanTargets;
+ }
+
+ public void setScanTargets(File[] scanTargets)
+ {
+ this.scanTargets = scanTargets;
+ }
+
+ public ScanTargetPattern[] getScanTargetPatterns()
+ {
+ return scanTargetPatterns;
+ }
+
+ public void setScanTargetPatterns(ScanTargetPattern[] scanTargetPatterns)
+ {
+ this.scanTargetPatterns = scanTargetPatterns;
+ }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
new file mode 100644
index 00000000000..6f92465b815
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarExplodedMojo.java
@@ -0,0 +1,173 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ *
+ *
+ * This goal is used to assemble your webapp into an exploded war and automatically deploy it to Jetty.
+ *
+ *
+ * Once invoked, the plugin can be configured to run continuously, scanning for changes in the pom.xml and
+ * to WEB-INF/web.xml, WEB-INF/classes or WEB-INF/lib and hot redeploy when a change is detected.
+ *
+ *
+ * You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ * This can be used, for example, to deploy a static webapp that is not part of your maven build.
+ *
+ *
+ * There is a reference guide to the configuration parameters for this plugin, and more detailed information
+ * with examples in the Configuration Guide.
+ *
+ *
+ *@goal run-exploded
+ *@requiresDependencyResolution compile+runtime
+ *@execute phase=package
+ */
+public class JettyRunWarExplodedMojo extends AbstractJettyMojo
+{
+
+
+
+ /**
+ * The location of the war file.
+ *
+ * @parameter alias="webApp" expression="${project.build.directory}/${project.build.finalName}"
+ * @required
+ */
+ private File war;
+
+
+
+
+
+
+ /**
+ *
+ * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ */
+ public void checkPomConfiguration() throws MojoExecutionException
+ {
+ return;
+ }
+
+ /**
+ * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#configureScanner()
+ */
+ public void configureScanner() throws MojoExecutionException
+ {
+ final ArrayList scanList = new ArrayList();
+ scanList.add(getProject().getFile());
+ File webInfDir = new File(war,"WEB-INF");
+ scanList.add(new File(webInfDir, "web.xml"));
+ File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
+ if (jettyWebXmlFile != null)
+ scanList.add(jettyWebXmlFile);
+ File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
+ if (jettyEnvXmlFile.exists())
+ scanList.add(jettyEnvXmlFile);
+ scanList.add(new File(webInfDir, "classes"));
+ scanList.add(new File(webInfDir, "lib"));
+ setScanList(scanList);
+
+ ArrayList listeners = new ArrayList();
+ listeners.add(new Scanner.BulkListener()
+ {
+ public void filesChanged(List changes)
+ {
+ try
+ {
+ boolean reconfigure = changes.contains(getProject().getFile().getCanonicalPath());
+ restartWebApp(reconfigure);
+ }
+ catch (Exception e)
+ {
+ getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+ }
+ }
+ });
+ setScannerListeners(listeners);
+ }
+
+
+
+
+ public void restartWebApp(boolean reconfigureScanner) throws Exception
+ {
+ getLog().info("Restarting webapp");
+ getLog().debug("Stopping webapp ...");
+ webApp.stop();
+ getLog().debug("Reconfiguring webapp ...");
+
+ checkPomConfiguration();
+
+ // check if we need to reconfigure the scanner,
+ // which is if the pom changes
+ if (reconfigureScanner)
+ {
+ getLog().info("Reconfiguring scanner after change to pom.xml ...");
+ ArrayList scanList = getScanList();
+ scanList.clear();
+ scanList.add(getProject().getFile());
+ File webInfDir = new File(war,"WEB-INF");
+ scanList.add(new File(webInfDir, "web.xml"));
+ File jettyWebXmlFile = findJettyWebXmlFile(webInfDir);
+ if (jettyWebXmlFile != null)
+ scanList.add(jettyWebXmlFile);
+ File jettyEnvXmlFile = new File(webInfDir, "jetty-env.xml");
+ if (jettyEnvXmlFile.exists())
+ scanList.add(jettyEnvXmlFile);
+ scanList.add(new File(webInfDir, "classes"));
+ scanList.add(new File(webInfDir, "lib"));
+ setScanList(scanList);
+ getScanner().setScanDirs(scanList);
+ }
+
+ getLog().debug("Restarting webapp ...");
+ webApp.start();
+ getLog().info("Restart completed.");
+ }
+
+
+
+ public void configureWebApplication () throws Exception
+ {
+ super.configureWebApplication();
+ webApp.setWar(war.getCanonicalPath());
+ }
+
+ public void execute () throws MojoExecutionException, MojoFailureException
+ {
+ super.execute();
+ }
+
+
+
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
new file mode 100644
index 00000000000..ae821a56a4d
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunWarMojo.java
@@ -0,0 +1,155 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+/**
+ *
+ * This goal is used to assemble your webapp into a war and automatically deploy it to Jetty.
+ *
+ *
+ * Once invoked, the plugin can be configured to run continuously, scanning for changes in the project and to the
+ * war file and automatically performing a
+ * hot redeploy when necessary.
+ *
+ *
+ * You may also specify the location of a jetty.xml file whose contents will be applied before any plugin configuration.
+ * This can be used, for example, to deploy a static webapp that is not part of your maven build.
+ *
+ *
+ * There is a reference guide to the configuration parameters for this plugin, and more detailed information
+ * with examples in the Configuration Guide.
+ *
+ *
+ * @goal run-war
+ * @requiresDependencyResolution compile+runtime
+ * @execute phase="package"
+ * @description Runs jetty on a war file
+ *
+ */
+public class JettyRunWarMojo extends AbstractJettyMojo
+{
+
+ /**
+ * The location of the war file.
+ * @parameter expression="${project.build.directory}/${project.build.finalName}.war"
+ * @required
+ */
+ private File war;
+
+
+
+ /**
+ * @see org.apache.maven.plugin.Mojo#execute()
+ */
+ public void execute() throws MojoExecutionException, MojoFailureException
+ {
+ super.execute();
+ }
+
+
+
+ public void configureWebApplication () throws Exception
+ {
+ super.configureWebApplication();
+
+ webApp.setWar(war.getCanonicalPath());
+ }
+
+
+
+ /**
+ * @see org.eclipse.jetty.maven.plugin.AbstractJettyMojo#checkPomConfiguration()
+ */
+ public void checkPomConfiguration() throws MojoExecutionException
+ {
+ return;
+ }
+
+
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jetty.server.plugin.AbstractJettyMojo#configureScanner()
+ */
+ public void configureScanner() throws MojoExecutionException
+ {
+ final ArrayList scanList = new ArrayList();
+ scanList.add(getProject().getFile());
+ scanList.add(war);
+ setScanList(scanList);
+
+ ArrayList listeners = new ArrayList();
+ listeners.add(new Scanner.BulkListener()
+ {
+ public void filesChanged(List changes)
+ {
+ try
+ {
+ boolean reconfigure = changes.contains(getProject().getFile().getCanonicalPath());
+ restartWebApp(reconfigure);
+ }
+ catch (Exception e)
+ {
+ getLog().error("Error reconfiguring/restarting webapp after change in watched files",e);
+ }
+ }
+ });
+ setScannerListeners(listeners);
+ }
+
+
+ public void restartWebApp(boolean reconfigureScanner) throws Exception
+ {
+ getLog().info("Restarting webapp ...");
+ getLog().debug("Stopping webapp ...");
+ webApp.stop();
+ getLog().debug("Reconfiguring webapp ...");
+
+ checkPomConfiguration();
+
+ // check if we need to reconfigure the scanner,
+ // which is if the pom changes
+ if (reconfigureScanner)
+ {
+ getLog().info("Reconfiguring scanner after change to pom.xml ...");
+ ArrayList scanList = getScanList();
+ scanList.clear();
+ scanList.add(getProject().getFile());
+ scanList.add(war);
+ setScanList(scanList);
+ getScanner().setScanDirs(scanList);
+ }
+
+ getLog().debug("Restarting webapp ...");
+ webApp.start();
+ getLog().info("Restart completed.");
+ }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
new file mode 100644
index 00000000000..a91181e4cae
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyServer.java
@@ -0,0 +1,128 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.RequestLog;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.SelectChannelConnector;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+/**
+ * JettyServer
+ *
+ * Maven jetty plugin version of a wrapper for the Server class.
+ *
+ */
+public class JettyServer extends org.eclipse.jetty.server.Server
+{
+ public static int DEFAULT_PORT = 8080;
+ public static int DEFAULT_MAX_IDLE_TIME = 30000;
+
+
+ private RequestLog requestLog;
+ private ContextHandlerCollection contexts;
+
+
+ public JettyServer()
+ {
+ super();
+ setStopAtShutdown(true);
+ //make sure Jetty does not use URLConnection caches with the plugin
+ Resource.setDefaultUseCaches(false);
+ }
+
+
+ public void setRequestLog (RequestLog requestLog)
+ {
+ this.requestLog = requestLog;
+ }
+
+ /**
+ * @see org.eclipse.jetty.server.Server#doStart()
+ */
+ public void doStart() throws Exception
+ {
+ super.doStart();
+ }
+
+
+ /**
+ * @see org.eclipse.jetty.server.handler.HandlerCollection#addHandler(org.eclipse.jetty.server.Handler)
+ */
+ public void addWebApplication(WebAppContext webapp) throws Exception
+ {
+ contexts.addHandler (webapp);
+ }
+
+
+ /**
+ * Set up the handler structure to receive a webapp.
+ * Also put in a DefaultHandler so we get a nice page
+ * than a 404 if we hit the root and the webapp's
+ * context isn't at root.
+ * @throws Exception
+ */
+ public void configureHandlers () throws Exception
+ {
+ DefaultHandler defaultHandler = new DefaultHandler();
+ RequestLogHandler requestLogHandler = new RequestLogHandler();
+ if (this.requestLog != null)
+ requestLogHandler.setRequestLog(this.requestLog);
+
+ contexts = (ContextHandlerCollection)super.getChildHandlerByClass(ContextHandlerCollection.class);
+ if (contexts==null)
+ {
+ contexts = new ContextHandlerCollection();
+ HandlerCollection handlers = (HandlerCollection)super.getChildHandlerByClass(HandlerCollection.class);
+ if (handlers==null)
+ {
+ handlers = new HandlerCollection();
+ super.setHandler(handlers);
+ handlers.setHandlers(new Handler[]{contexts, defaultHandler, requestLogHandler});
+ }
+ else
+ {
+ handlers.addHandler(contexts);
+ }
+ }
+ }
+
+
+
+
+ public Connector createDefaultConnector(Server server, String portnum) throws Exception
+ {
+ SelectChannelConnector connector = new SelectChannelConnector(server);
+ int port = ((portnum==null||portnum.equals(""))?DEFAULT_PORT:Integer.parseInt(portnum.trim()));
+ connector.setPort(port);
+ connector.setIdleTimeout(DEFAULT_MAX_IDLE_TIME);
+
+ return connector;
+ }
+
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java
new file mode 100644
index 00000000000..1c6295563c3
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStartMojo.java
@@ -0,0 +1,40 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+
+/**
+ *
+ * This goal is similar to the jetty:run goal, EXCEPT that it is designed to be bound to an execution inside your pom, rather
+ * than being run from the command line.
+ *
+ *
+ * When using it, be careful to ensure that you bind it to a phase in which all necessary generated files and classes for the webapp
+ * will have been created. If you run it from the command line, then also ensure that all necessary generated files and classes for
+ * the webapp already exist.
+ *
+ *
+ * @goal start
+ * @requiresDependencyResolution test
+ * @execute phase="validate"
+ * @description Runs jetty directly from a maven project from a binding to an execution in your pom
+ */
+public class JettyStartMojo extends JettyRunMojo
+{
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
new file mode 100644
index 00000000000..b2148b71810
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyStopMojo.java
@@ -0,0 +1,100 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+
+/**
+ * JettyStopMojo - stops a running instance of jetty.
+ * The ff are required:
+ * -DstopKey=someKey
+ * -DstopPort=somePort
+ *
+ * @goal stop
+ * @description Stops jetty that is configured with <stopKey> and <stopPort>.
+ */
+
+public class JettyStopMojo extends AbstractMojo
+{
+
+ /**
+ * Port to listen to stop jetty on sending stop command
+ * @parameter
+ * @required
+ */
+ protected int stopPort;
+
+ /**
+ * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey>
+ * -DSTOP.PORT=<stopPort> -jar start.jar --stop
+ * @parameter
+ * @required
+ */
+ protected String stopKey;
+
+ public void execute() throws MojoExecutionException, MojoFailureException
+ {
+ if (stopPort <= 0)
+ throw new MojoExecutionException("Please specify a valid port");
+ if (stopKey == null)
+ throw new MojoExecutionException("Please specify a valid stopKey");
+
+ try
+ {
+ Socket s=new Socket(InetAddress.getByName("127.0.0.1"),stopPort);
+ s.setSoLinger(false, 0);
+
+ OutputStream out=s.getOutputStream();
+ out.write((stopKey+"\r\nstop\r\n").getBytes());
+ out.flush();
+ s.close();
+ }
+ catch (ConnectException e)
+ {
+ getLog().info("Jetty not running!");
+ }
+ catch (Exception e)
+ {
+ getLog().error(e);
+ }
+ }
+
+ public int getStopPort() {
+ return stopPort;
+ }
+
+ public void setStopPort(int stopPort) {
+ this.stopPort = stopPort;
+ }
+
+ public String getStopKey() {
+ return stopKey;
+ }
+
+ public void setStopKey(String stopKey) {
+ this.stopKey = stopKey;
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
new file mode 100644
index 00000000000..f6b4e122a5a
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyWebAppContext.java
@@ -0,0 +1,394 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jetty.annotations.AnnotationConfiguration;
+import org.eclipse.jetty.plus.webapp.EnvConfiguration;
+import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.Configuration;
+import org.eclipse.jetty.webapp.FragmentConfiguration;
+import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
+import org.eclipse.jetty.webapp.MetaInfConfiguration;
+import org.eclipse.jetty.webapp.TagLibConfiguration;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebXmlConfiguration;
+
+/**
+ * JettyWebAppContext
+ *
+ * Extends the WebAppContext to specialize for the maven environment.
+ * We pass in the list of files that should form the classpath for
+ * the webapp when executing in the plugin, and any jetty-env.xml file
+ * that may have been configured.
+ *
+ */
+public class JettyWebAppContext extends WebAppContext
+{
+ private static final Logger LOG = Log.getLogger(JettyWebAppContext.class);
+
+ private static final String WEB_INF_CLASSES_PREFIX = "/WEB-INF/classes";
+ private static final String WEB_INF_LIB_PREFIX = "/WEB-INF/lib";
+
+ private File classes = null;
+ private File testClasses = null;
+ private final List webInfClasses = new ArrayList();
+ private final List webInfJars = new ArrayList();
+ private final Map webInfJarMap = new HashMap();
+ private final EnvConfiguration envConfig;
+ private List classpathFiles; //webInfClasses+testClasses+webInfJars
+ private String jettyEnvXml;
+ private List overlays;
+
+ /**
+ * @deprecated The value of this parameter will be ignored by the plugin. Overlays will always be unpacked.
+ */
+ private boolean unpackOverlays;
+
+ /**
+ * @deprecated The value of this parameter will be ignored by the plugin. This option will be always disabled.
+ */
+ private boolean copyWebInf;
+
+ private boolean baseAppFirst = true;
+
+ public JettyWebAppContext ()
+ throws Exception
+ {
+ super();
+ setConfigurations(new Configuration[]{
+ new MavenWebInfConfiguration(),
+ new WebXmlConfiguration(),
+ new MetaInfConfiguration(),
+ new FragmentConfiguration(),
+ envConfig = new EnvConfiguration(),
+ new AnnotationConfiguration(),
+ new org.eclipse.jetty.plus.webapp.PlusConfiguration(),
+ new JettyWebXmlConfiguration(),
+ new TagLibConfiguration()
+ });
+ // Turn off copyWebInf option as it is not applicable for plugin.
+ super.setCopyWebInf(false);
+ }
+
+ public boolean getUnpackOverlays()
+ {
+ return unpackOverlays;
+ }
+
+ public void setUnpackOverlays(boolean unpackOverlays)
+ {
+ this.unpackOverlays = unpackOverlays;
+ }
+
+ public List getClassPathFiles()
+ {
+ return this.classpathFiles;
+ }
+
+ public void setOverlays (List overlays)
+ {
+ this.overlays = overlays;
+ }
+
+ public List getOverlays ()
+ {
+ return this.overlays;
+ }
+
+ public void setJettyEnvXml (String jettyEnvXml)
+ {
+ this.jettyEnvXml = jettyEnvXml;
+ }
+
+ public String getJettyEnvXml()
+ {
+ return this.jettyEnvXml;
+ }
+
+
+ public void setClasses(File dir)
+ {
+ classes = dir;
+ }
+
+ public File getClasses()
+ {
+ return classes;
+ }
+
+ public void setWebInfLib (List jars)
+ {
+ webInfJars.addAll(jars);
+ }
+
+
+ public void setTestClasses (File dir)
+ {
+ testClasses = dir;
+ }
+
+
+ public File getTestClasses ()
+ {
+ return testClasses;
+ }
+
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public void setCopyWebInf(boolean value)
+ {
+ copyWebInf = value;
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ public boolean isCopyWebInf()
+ {
+ return copyWebInf;
+ }
+
+ /* ------------------------------------------------------------ */
+ public void setBaseAppFirst(boolean value)
+ {
+ baseAppFirst = value;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean getBaseAppFirst()
+ {
+ return baseAppFirst;
+ }
+
+ /* ------------------------------------------------------------ */
+ /**
+ * This method is provided as a convenience for jetty maven plugin configuration
+ * @param resourceBases Array of resources strings to set as a {@link ResourceCollection}. Each resource string may be a comma separated list of resources
+ * @see Resource
+ */
+ public void setResourceBases(String[] resourceBases)
+ {
+ List resources = new ArrayList();
+ for (String rl:resourceBases)
+ {
+ String[] rs = rl.split(" *, *");
+ for (String r:rs)
+ resources.add(r);
+ }
+
+ setBaseResource(new ResourceCollection(resources.toArray(new String[resources.size()])));
+ }
+
+ public List getWebInfLib()
+ {
+ return webInfJars;
+ }
+
+ public void doStart () throws Exception
+ {
+ //Set up the pattern that tells us where the jars are that need scanning for
+ //stuff like taglibs so we can tell jasper about it (see TagLibConfiguration)
+ String tmp = (String)getAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern");
+
+ tmp = addPattern(tmp, ".*/.*jsp-api-[^/]*\\.jar$");
+ tmp = addPattern(tmp, ".*/.*jsp-[^/]*\\.jar$");
+ tmp = addPattern(tmp, ".*/.*taglibs[^/]*\\.jar$");
+ tmp = addPattern(tmp, ".*/.*jstl[^/]*\\.jar$");
+ tmp = addPattern(tmp, ".*/.*jsf-impl-[^/]*\\.jar$"); // add in 2 most popular jsf impls
+ tmp = addPattern(tmp, ".*/.*javax.faces-[^/]*\\.jar$");
+ tmp = addPattern(tmp, ".*/.*myfaces-impl-[^/]*\\.jar$");
+
+ setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", tmp);
+
+ //Set up the classes dirs that comprises the equivalent of WEB-INF/classes
+ if (testClasses != null)
+ webInfClasses.add(testClasses);
+ if (classes != null)
+ webInfClasses.add(classes);
+
+ // Set up the classpath
+ classpathFiles = new ArrayList();
+ classpathFiles.addAll(webInfClasses);
+ classpathFiles.addAll(webInfJars);
+
+
+ // Initialize map containing all jars in /WEB-INF/lib
+ webInfJarMap.clear();
+ for (File file : webInfJars)
+ {
+ // Return all jar files from class path
+ String fileName = file.getName();
+ if (fileName.endsWith(".jar"))
+ webInfJarMap.put(fileName, file);
+ }
+
+ if (this.jettyEnvXml != null)
+ envConfig.setJettyEnvXml(Resource.toURL(new File(this.jettyEnvXml)));
+
+ //setShutdown(false);
+ super.doStart();
+ }
+
+ public void doStop () throws Exception
+ {
+ //setShutdown(true);
+ //just wait a little while to ensure no requests are still being processed
+ Thread.currentThread().sleep(500L);
+ super.doStop();
+ }
+
+ @Override
+ public Resource getResource(String uriInContext) throws MalformedURLException
+ {
+ Resource resource = null;
+ // Try to get regular resource
+ resource = super.getResource(uriInContext);
+
+ // If no regular resource exists check for access to /WEB-INF/lib or /WEB-INF/classes
+ if ((resource == null || !resource.exists()) && uriInContext != null && classes != null)
+ {
+ String uri = URIUtil.canonicalPath(uriInContext);
+ if (uri == null)
+ return null;
+
+ try
+ {
+ // Replace /WEB-INF/classes with candidates for the classpath
+ if (uri.startsWith(WEB_INF_CLASSES_PREFIX))
+ {
+ if (uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX) || uri.equalsIgnoreCase(WEB_INF_CLASSES_PREFIX+"/"))
+ {
+ //exact match for a WEB-INF/classes, so preferentially return the resource matching the web-inf classes
+ //rather than the test classes
+ if (classes != null)
+ return Resource.newResource(classes);
+ else if (testClasses != null)
+ return Resource.newResource(testClasses);
+ }
+ else
+ {
+ //try matching
+ Resource res = null;
+ int i=0;
+ while (res == null && (i < webInfClasses.size()))
+ {
+ String newPath = uri.replace(WEB_INF_CLASSES_PREFIX, webInfClasses.get(i).getPath());
+ res = Resource.newResource(newPath);
+ if (!res.exists())
+ {
+ res = null;
+ i++;
+ }
+ }
+ return res;
+ }
+ }
+ else if (uri.startsWith(WEB_INF_LIB_PREFIX))
+ {
+ // Return the real jar file for all accesses to
+ // /WEB-INF/lib/*.jar
+ String jarName = uri.replace(WEB_INF_LIB_PREFIX, "");
+ if (jarName.startsWith("/") || jarName.startsWith("\\"))
+ jarName = jarName.substring(1);
+ if (jarName.length()==0)
+ return null;
+ File jarFile = webInfJarMap.get(jarName);
+ if (jarFile != null)
+ return Resource.newResource(jarFile.getPath());
+
+ return null;
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ throw e;
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ return resource;
+ }
+
+ @Override
+ public Set getResourcePaths(String path)
+ {
+ // Try to get regular resource paths
+ Set paths = super.getResourcePaths(path);
+
+ // If no paths are returned check for virtual paths /WEB-INF/classes and /WEB-INF/lib
+ if (paths.isEmpty() && path != null)
+ {
+ path = URIUtil.canonicalPath(path);
+ if (path.startsWith(WEB_INF_LIB_PREFIX))
+ {
+ paths = new TreeSet();
+ for (String fileName : webInfJarMap.keySet())
+ {
+ // Return all jar files from class path
+ paths.add(WEB_INF_LIB_PREFIX + "/" + fileName);
+ }
+ }
+ else if (path.startsWith(WEB_INF_CLASSES_PREFIX))
+ {
+ int i=0;
+
+ while (paths.isEmpty() && (i < webInfClasses.size()))
+ {
+ String newPath = path.replace(WEB_INF_CLASSES_PREFIX, webInfClasses.get(i).getPath());
+ paths = super.getResourcePaths(newPath);
+ i++;
+ }
+ }
+ }
+ return paths;
+ }
+
+ public String addPattern (String s, String pattern)
+ {
+ if (s == null)
+ s = "";
+ else
+ s = s.trim();
+
+ if (!s.contains(pattern))
+ {
+ if (s.length() != 0)
+ s = s + "|";
+ s = s + pattern;
+ }
+
+ return s;
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
new file mode 100644
index 00000000000..bbfd64cf433
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java
@@ -0,0 +1,231 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.LazyList;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.WebAppClassLoader;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebInfConfiguration;
+
+public class MavenWebInfConfiguration extends WebInfConfiguration
+{
+ private static final Logger LOG = Log.getLogger(MavenWebInfConfiguration.class);
+
+ protected Resource _originalResourceBase;
+ protected Resource[] _unpackedOverlays;
+
+
+ public void configure(WebAppContext context) throws Exception
+ {
+ JettyWebAppContext jwac = (JettyWebAppContext)context;
+ if (jwac.getClassPathFiles() != null)
+ {
+ if (LOG.isDebugEnabled()) LOG.debug("Setting up classpath ...");
+
+ //put the classes dir and all dependencies into the classpath
+ Iterator itor = jwac.getClassPathFiles().iterator();
+ while (itor.hasNext())
+ ((WebAppClassLoader)context.getClassLoader()).addClassPath(((File)itor.next()).getCanonicalPath());
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("Classpath = "+((URLClassLoader)context.getClassLoader()).getURLs());
+ }
+ super.configure(context);
+
+ // knock out environmental maven and plexus classes from webAppContext
+ String[] existingServerClasses = context.getServerClasses();
+ String[] newServerClasses = new String[2+(existingServerClasses==null?0:existingServerClasses.length)];
+ newServerClasses[0] = "org.apache.maven.";
+ newServerClasses[1] = "org.codehaus.plexus.";
+ System.arraycopy( existingServerClasses, 0, newServerClasses, 2, existingServerClasses.length );
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("Server classes:");
+ for (int i=0;i 0)
+ {
+ if (jwac.getBaseAppFirst())
+ {
+ System.arraycopy(origResources,0,newResources,0,origSize);
+
+ offset = origSize;
+ }
+ else
+ {
+ System.arraycopy(origResources,0,newResources,overlaySize,origSize);
+ }
+ }
+
+ // Overlays are always unpacked
+ _unpackedOverlays = new Resource[overlaySize];
+ List overlays = jwac.getOverlays();
+ for (int idx=0; idx0)
+ {
+ try
+ {
+ for (int i=0; i<_unpackedOverlays.length; i++)
+ {
+ IO.delete(_unpackedOverlays[i].getFile());
+ }
+ }
+ catch (IOException e)
+ {
+ LOG.ignore(e);
+ }
+ }
+ super.deconfigure(context);
+ //restore whatever the base resource was before we might have included overlaid wars
+ context.setBaseResource(_originalResourceBase);
+
+ }
+
+
+ /**
+ * Get the jars to examine from the files from which we have
+ * synthesized the classpath. Note that the classpath is not
+ * set at this point, so we cannot get them from the classpath.
+ * @param context
+ * @return the list of jars found
+ */
+ protected List findJars (WebAppContext context)
+ throws Exception
+ {
+ List list = new ArrayList();
+ JettyWebAppContext jwac = (JettyWebAppContext)context;
+ if (jwac.getClassPathFiles() != null)
+ {
+ for (File f: jwac.getClassPathFiles())
+ {
+ if (f.getName().toLowerCase().endsWith(".jar"))
+ {
+ try
+ {
+ list.add(Resource.newResource(f.toURI()));
+ }
+ catch (Exception e)
+ {
+ LOG.warn("Bad url ", e);
+ }
+ }
+ }
+ }
+
+ List superList = super.findJars(context);
+ if (superList != null)
+ list.addAll(superList);
+ return list;
+ }
+
+ protected Resource unpackOverlay (WebAppContext context, Resource overlay)
+ throws IOException
+ {
+ //resolve if not already resolved
+ resolveTempDirectory(context);
+
+
+ //Get the name of the overlayed war and unpack it to a dir of the
+ //same name in the temporary directory
+ String name = overlay.getName();
+ if (name.endsWith("!/"))
+ name = name.substring(0,name.length()-2);
+ int i = name.lastIndexOf('/');
+ if (i>0)
+ name = name.substring(i+1,name.length());
+ name = name.replace('.', '_');
+ File dir = new File(context.getTempDirectory(), name);
+ overlay.copyTo(dir);
+ Resource unpackedOverlay = Resource.newResource(dir.getCanonicalPath());
+ return unpackedOverlay;
+ }
+
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
new file mode 100644
index 00000000000..051277c7f54
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Monitor.java
@@ -0,0 +1,149 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.eclipse.jetty.server.Server;
+
+
+
+
+
+/**
+ * Monitor
+ *
+ * Listens for stop commands eg via mvn jetty:stop and
+ * causes jetty to stop either by exiting the jvm, or
+ * by stopping the Server instances. The choice of
+ * behaviour is controlled by either passing true
+ * (exit jvm) or false (stop Servers) in the constructor.
+ *
+ */
+public class Monitor extends Thread
+{
+ private String _key;
+ private Server[] _servers;
+
+ ServerSocket _serverSocket;
+ boolean _kill;
+
+ public Monitor(int port, String key, Server[] servers, boolean kill)
+ throws UnknownHostException, IOException
+ {
+ if (port <= 0)
+ throw new IllegalStateException ("Bad stop port");
+ if (key==null)
+ throw new IllegalStateException("Bad stop key");
+
+ _key = key;
+ _servers = servers;
+ _kill = kill;
+ setDaemon(true);
+ setName("StopJettyPluginMonitor");
+ InetSocketAddress address = new InetSocketAddress("127.0.0.1", port);
+ _serverSocket=new ServerSocket();
+ _serverSocket.setReuseAddress(true);
+ try
+ {
+ _serverSocket.bind(address,1);
+ }
+ catch (IOException x)
+ {
+ System.out.println("Error binding to stop port 127.0.0.1:"+port);
+ throw x;
+ }
+ }
+
+ public void run()
+ {
+ while (_serverSocket != null)
+ {
+ Socket socket = null;
+ try
+ {
+ socket = _serverSocket.accept();
+ socket.setSoLinger(false, 0);
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+
+ String key = lin.readLine();
+ if (!_key.equals(key)) continue;
+ String cmd = lin.readLine();
+ if ("stop".equals(cmd))
+ {
+ try{socket.close();}catch (Exception e){e.printStackTrace();}
+ try{socket.close();}catch (Exception e){e.printStackTrace();}
+ try{_serverSocket.close();}catch (Exception e){e.printStackTrace();}
+
+ _serverSocket = null;
+
+ if (_kill)
+ {
+ System.out.println("Killing Jetty");
+ System.exit(0);
+ }
+ else
+ {
+ for (int i=0; _servers != null && i < _servers.length; i++)
+ {
+ try
+ {
+ System.out.println("Stopping server "+i);
+ _servers[i].stop();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ else
+ System.out.println("Unsupported monitor operation");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ if (socket != null)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+ socket = null;
+ }
+ }
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java
new file mode 100644
index 00000000000..822105e65bb
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/PluginLog.java
@@ -0,0 +1,44 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import org.apache.maven.plugin.logging.Log;
+
+/**
+ * PluginLog
+ *
+ * Convenience class to provide access to the plugin
+ * Log for non-mojo classes.
+ *
+ */
+public class PluginLog
+{
+ private static Log log = null;
+
+ public static void setLog(Log l)
+ {
+ log = l;
+ }
+
+ public static Log getLog()
+ {
+ return log;
+ }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java
new file mode 100644
index 00000000000..91daa27c1ad
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/ScanTargetPattern.java
@@ -0,0 +1,88 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ScanTargetPattern
+ *
+ * Utility class to provide the ability for the mvn jetty:run
+ * mojo to be able to specify filesets of extra files to
+ * regularly scan for changes in order to redeploy the webapp.
+ *
+ * For example:
+ *
+ * <scanTargetPattern>
+ * <directory>/some/place</directory>
+ * <includes>
+ * <include>some ant pattern here </include>
+ * <include>some ant pattern here </include>
+ * </includes>
+ * <excludes>
+ * <exclude>some ant pattern here </exclude>
+ * <exclude>some ant pattern here </exclude>
+ * </excludes>
+ * </scanTargetPattern>
+ */
+public class ScanTargetPattern
+{
+ private File _directory;
+ private List _includes = Collections.EMPTY_LIST;
+ private List _excludes = Collections.EMPTY_LIST;
+
+ /**
+ * @return the _directory
+ */
+ public File getDirectory()
+ {
+ return _directory;
+ }
+
+ /**
+ * @param directory the directory to set
+ */
+ public void setDirectory(File directory)
+ {
+ this._directory = directory;
+ }
+
+ public void setIncludes (List includes)
+ {
+ _includes= includes;
+ }
+
+ public void setExcludes(List excludes)
+ {
+ _excludes = excludes;
+ }
+
+ public List getIncludes()
+ {
+ return _includes;
+ }
+
+ public List getExcludes()
+ {
+ return _excludes;
+ }
+
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
new file mode 100644
index 00000000000..bd148d1eb01
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java
@@ -0,0 +1,318 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+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;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.ResourceCollection;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.xml.XmlConfiguration;
+
+public class Starter
+{
+ public static final String PORT_SYSPROPERTY = "jetty.port";
+ private static final Logger LOG = Log.getLogger(Starter.class);
+
+ private List jettyXmls; // list of jetty.xml config files to apply - Mandatory
+ private File contextXml; //name of context xml file to configure the webapp - Mandatory
+
+ private JettyServer server;
+ private JettyWebAppContext webApp;
+ private Monitor monitor;
+
+ private int stopPort=0;
+ private String stopKey=null;
+ private Properties props;
+ private String token;
+
+
+
+ public void configureJetty () throws Exception
+ {
+ LOG.debug("Starting Jetty Server ...");
+
+ this.server = new JettyServer();
+
+ //apply any configs from jetty.xml files first
+ applyJettyXml ();
+
+ // if the user hasn't configured a connector in the jetty.xml
+ //then use a default
+ Connector[] connectors = this.server.getConnectors();
+ if (connectors == null|| connectors.length == 0)
+ {
+ //if a SystemProperty -Djetty.port= has been supplied, use that as the default port
+ connectors = new Connector[] { this.server.createDefaultConnector(server, System.getProperty(PORT_SYSPROPERTY, null)) };
+ this.server.setConnectors(connectors);
+ }
+
+ //check that everything got configured, and if not, make the handlers
+ HandlerCollection handlers = (HandlerCollection) server.getChildHandlerByClass(HandlerCollection.class);
+ if (handlers == null)
+ {
+ handlers = new HandlerCollection();
+ server.setHandler(handlers);
+ }
+
+ //check if contexts already configured, create if not
+ this.server.configureHandlers();
+
+ webApp = new JettyWebAppContext();
+
+ //configure webapp from properties file describing unassembled webapp
+ configureWebApp();
+
+ //set up the webapp from the context xml file provided
+ //NOTE: just like jetty:run mojo this means that the context file can
+ //potentially override settings made in the pom. Ideally, we'd like
+ //the pom to override the context xml file, but as the other mojos all
+ //configure a WebAppContext in the pom (the element), it is
+ //already configured by the time the context xml file is applied.
+ if (contextXml != null)
+ {
+ XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(contextXml));
+ xmlConfiguration.getIdMap().put("Server",server);
+ xmlConfiguration.configure(webApp);
+ }
+
+ this.server.addWebApplication(webApp);
+
+ System.err.println("STOP PORT="+stopPort+", STOP KEY="+stopKey);
+ if(stopPort>0 && stopKey!=null)
+ {
+ monitor = new Monitor(stopPort, stopKey, new Server[]{server}, true);
+ }
+ }
+
+
+ public void configureWebApp ()
+ throws Exception
+ {
+ if (props == null)
+ return;
+
+ //apply a properties file that defines the things that we configure in the jetty:run plugin:
+ // - the context path
+ String str = (String)props.get("context.path");
+ if (str != null)
+ webApp.setContextPath(str);
+
+ // - web.xml
+ str = (String)props.get("web.xml");
+ if (str != null)
+ webApp.setDescriptor(str);
+
+ // - the tmp directory
+ str = (String)props.getProperty("tmp.dir");
+ if (str != null)
+ webApp.setTempDirectory(new File(str.trim()));
+
+ // - the base directory
+ str = (String)props.getProperty("base.dir");
+ if (str != null && !"".equals(str.trim()))
+ webApp.setWar(str);
+
+ // - the multiple comma separated resource dirs
+ str = (String)props.getProperty("res.dirs");
+ if (str != null && !"".equals(str.trim()))
+ {
+ ResourceCollection resources = new ResourceCollection(str);
+ webApp.setBaseResource(resources);
+ }
+
+ // - overlays
+ str = (String)props.getProperty("overlay.files");
+ if (str != null && !"".equals(str.trim()))
+ {
+ List overlays = new ArrayList();
+ String[] names = str.split(",");
+ for (int j=0; names != null && j < names.length; j++)
+ overlays.add(Resource.newResource("jar:"+Resource.toURL(new File(names[j].trim())).toString()+"!/"));
+ webApp.setOverlays(overlays);
+ }
+
+ // - the equivalent of web-inf classes
+ str = (String)props.getProperty("classes.dir");
+ if (str != null && !"".equals(str.trim()))
+ {
+ webApp.setClasses(new File(str));
+ }
+
+ str = (String)props.getProperty("testClasses.dir");
+ if (str != null && !"".equals(str.trim()))
+ {
+ webApp.setTestClasses(new File(str));
+ }
+
+
+ // - the equivalent of web-inf lib
+ str = (String)props.getProperty("lib.jars");
+ if (str != null && !"".equals(str.trim()))
+ {
+ List jars = new ArrayList();
+ String[] names = str.split(",");
+ for (int j=0; names != null && j < names.length; j++)
+ jars.add(new File(names[j].trim()));
+ webApp.setWebInfLib(jars);
+ }
+
+ }
+
+ public void getConfiguration (String[] args)
+ throws Exception
+ {
+ for (int i=0; i();
+ String[] names = args[++i].split(",");
+ for (int j=0; names!= null && j < names.length; j++)
+ {
+ jettyXmls.add(new File(names[j].trim()));
+ }
+ }
+
+ //--context-xml
+ if ("--context-xml".equals(args[i]))
+ {
+ contextXml = new File(args[++i]);
+ }
+
+ //--props
+ if ("--props".equals(args[i]))
+ {
+ File f = new File(args[++i].trim());
+ props = new Properties();
+ props.load(new FileInputStream(f));
+ }
+
+ //--token
+ if ("--token".equals(args[i]))
+ {
+ token = args[++i].trim();
+ }
+ }
+ }
+
+
+ public void run() throws Exception
+ {
+ if (monitor != null)
+ monitor.start();
+
+ LOG.info("Started Jetty Server");
+ server.start();
+ }
+
+
+ public void join () throws Exception
+ {
+ server.join();
+ }
+
+
+ public void communicateStartupResult (Exception e)
+ {
+ if (token != null)
+ {
+ if (e==null)
+ System.out.println(token);
+ else
+ System.out.println(token+"\t"+e.getMessage());
+ }
+ }
+
+
+ public void applyJettyXml() throws Exception
+ {
+ if (jettyXmls == null)
+ return;
+
+ for ( File xmlFile : jettyXmls )
+ {
+ LOG.info( "Configuring Jetty from xml configuration file = " + xmlFile.getCanonicalPath() );
+ XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.toURL(xmlFile));
+ xmlConfiguration.configure(this.server);
+ }
+ }
+
+
+
+
+ protected void prependHandler (Handler handler, HandlerCollection handlers)
+ {
+ if (handler == null || handlers == null)
+ return;
+
+ Handler[] existing = handlers.getChildHandlers();
+ Handler[] children = new Handler[existing.length + 1];
+ children[0] = handler;
+ System.arraycopy(existing, 0, children, 1, existing.length);
+ handlers.setHandlers(children);
+ }
+
+
+ public static final void main(String[] args)
+ {
+ if (args == null)
+ System.exit(1);
+
+ Starter starter = null;
+ try
+ {
+ starter = new Starter();
+ starter.getConfiguration(args);
+ starter.configureJetty();
+ starter.run();
+ starter.communicateStartupResult(null);
+ starter.join();
+ }
+ catch (Exception e)
+ {
+ starter.communicateStartupResult(e);
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java
new file mode 100644
index 00000000000..ec495d1cd8b
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperties.java
@@ -0,0 +1,79 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * SystemProperties
+ *
+ * Map of name to SystemProperty.
+ *
+ * When a SystemProperty instance is added, if it has not
+ * been already set (eg via the command line java system property)
+ * then it will be set.
+ */
+public class SystemProperties
+{
+ Map properties;
+ boolean force;
+
+ public SystemProperties()
+ {
+ properties = new HashMap();
+ }
+
+ public void setForce (boolean force)
+ {
+ this.force = force;
+ }
+
+ public boolean getForce ()
+ {
+ return this.force;
+ }
+
+
+ public void setSystemProperty (SystemProperty prop)
+ {
+ properties.put(prop.getName(), prop);
+ if (!force)
+ prop.setIfNotSetAlready();
+ else
+ prop.setAnyway();
+ }
+
+ public SystemProperty getSystemProperty(String name)
+ {
+ return (SystemProperty)properties.get(name);
+ }
+
+ public boolean containsSystemProperty(String name)
+ {
+ return properties.containsKey(name);
+ }
+
+ public List getSystemProperties ()
+ {
+ return new ArrayList(properties.values());
+ }
+}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java
new file mode 100644
index 00000000000..80a47d6c0d0
--- /dev/null
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/SystemProperty.java
@@ -0,0 +1,103 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.maven.plugin;
+
+/**
+ * SystemProperty
+ *
+ * Provides the ability to set System properties
+ * for the mojo execution. A value will only
+ * be set if it is not set already. That is, if
+ * it was set on the command line or by the system,
+ * it won't be overridden by settings in the
+ * plugin's configuration.
+ *
+ */
+public class SystemProperty
+{
+
+
+ private String name;
+ private String value;
+ private boolean isSet;
+
+ /**
+ * @return Returns the name.
+ */
+ public String getName()
+ {
+ return this.name;
+ }
+ /**
+ * @param name The name to set.
+ */
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getKey()
+ {
+ return this.name;
+ }
+
+ public void setKey (String name)
+ {
+ this.name = name;
+ }
+ /**
+ * @return Returns the value.
+ */
+ public String getValue()
+ {
+ return this.value;
+ }
+ /**
+ * @param value The value to set.
+ */
+ public void setValue(String value)
+ {
+ this.value = value;
+ }
+
+
+ public boolean isSet ()
+ {
+ return isSet;
+ }
+
+ /** Set a System.property with this value
+ * if it is not already set.
+ */
+ void setIfNotSetAlready()
+ {
+ if (System.getProperty(getName()) == null)
+ {
+ System.setProperty(getName(), (getValue()==null?"":getValue()));
+ isSet=true;
+ }
+ }
+
+ void setAnyway()
+ {
+ System.setProperty(getName(), (getValue()==null?"":getValue()));
+ isSet=true;
+ }
+
+}