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