All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.maven.plugin.JettyRunForkedMojo Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2014 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.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Random;
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.plugin.descriptor.PluginDescriptor;
import org.apache.maven.project.MavenProject;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;


/**
 * 

* 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 test * @execute phase="test-compile" * @description Runs Jetty in forked JVM on an unassembled webapp * */ public class JettyRunForkedMojo extends AbstractMojo { public static final String DEFAULT_WEBAPP_SRC = "src"+File.separator+"main"+File.separator+"webapp"; public static final String FAKE_WEBAPP = "webapp-tmp"; 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 alias="tmpDirectory" expression="${project.build.directory}/tmp" * @required * @readonly */ protected File tempDirectory; /** * Whether temporary directory contents should survive webapp restarts. * * @parameter default-value="false" */ private boolean persistTempDirectory; /** * 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; /** * Resource Bases * * @parameter * */ private String[] 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 / for jetty-9 * * @parameter expression="/" */ 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; /** * @parameter default-value="50" */ private int maxStartupLines; /** * The forked jetty instance */ private Process forkedProcess; /** * Random number generator */ private Random random; /** * ShutdownThread * * */ public class ShutdownThread extends Thread { public ShutdownThread() { super("RunForkedShutdown"); } public void run () { if (forkedProcess != null && waitForChild) { forkedProcess.destroy(); } } } /** * ConsoleStreamer * * 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); } } } /** * @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(); } /** * @return * @throws MojoExecutionException */ 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; } /** * @return * @throws MojoExecutionException */ 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 (tempDirectory != null) { if (!tempDirectory.exists()) tempDirectory.mkdirs(); props.put("tmp.dir", tempDirectory.getAbsolutePath()); } props.put("tmp.dir.persist", Boolean.toString(persistTempDirectory)); if (resourceBases == null) { //sort out base dir of webapp if (webAppSourceDirectory == null || !webAppSourceDirectory.exists()) { webAppSourceDirectory = new File (project.getBasedir(), DEFAULT_WEBAPP_SRC); if (!webAppSourceDirectory.exists()) { //try last resort of making a fake empty dir File target = new File(project.getBuild().getDirectory()); webAppSourceDirectory = new File(target, FAKE_WEBAPP); if (!webAppSourceDirectory.exists()) webAppSourceDirectory.mkdirs(); } } resourceBases = new String[] { webAppSourceDirectory.getAbsolutePath() }; } StringBuffer rb = new StringBuffer(resourceBases[0]); for (int i=1; i classDirs = getClassesDirs(); StringBuffer strbuff = new StringBuffer(); for (int i=0; i deps = getDependencyFiles(); strbuff.setLength(0); for (int i=0; i warArtifacts = getWarArtifacts(); for (int i=0; i configs = warPlugin.getMavenWarOverlayConfigs(); int i=0; for (OverlayConfig c:configs) { props.put("maven.war.overlay."+(i++), c.toString()); } try (OutputStream out = new BufferedOutputStream(new FileOutputStream(propsFile))) { props.store(out, "properties for forked webapp"); } return propsFile; } catch (Exception e) { throw new MojoExecutionException("Prepare webapp configuration", e); } } /** * @return */ 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; } /** * @return * @throws MalformedURLException * @throws IOException */ private List getWarArtifacts() throws MalformedURLException, IOException { List warArtifacts = new ArrayList(); for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); ) { Artifact artifact = (Artifact) iter.next(); if (artifact.getType().equals("war")) warArtifacts.add(artifact); } return warArtifacts; } /** * @return */ private List getDependencyFiles () { List dependencyFiles = new ArrayList(); for ( Iterator iter = project.getArtifacts().iterator(); iter.hasNext(); ) { Artifact artifact = (Artifact) iter.next(); // Test never appears here ! 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; } /** * @param artifact * @return */ 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; } /** * @return * @throws Exception */ 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; } /** * @throws MojoExecutionException */ 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())); PluginLog.getLog().info("Forked process starting"); if (waitForChild) { forkedProcess = builder.start(); startPump("STDOUT",forkedProcess.getInputStream()); startPump("STDERR",forkedProcess.getErrorStream()); int exitcode = forkedProcess.waitFor(); PluginLog.getLog().info("Forked execution exit: "+exitcode); } else { //merge stderr and stdout from child builder.redirectErrorStream(true); forkedProcess = builder.start(); //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 { String line = ""; try (InputStream is = forkedProcess.getInputStream(); LineNumberReader reader = new LineNumberReader(new InputStreamReader(is))) { int attempts = maxStartupLines; //max lines we'll read trying to get token while (attempts>0 && line != null) { --attempts; line = reader.readLine(); if (line != null && line.startsWith(token)) break; } } if (line != null && line.trim().equals(token)) PluginLog.getLog().info("Forked process started."); else { String err = (line == null?"":(line.startsWith(token)?line.substring(token.length()):line)); PluginLog.getLog().info("Forked process startup errors"+(!"".equals(err)?", received: "+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); } } /** * @return * @throws Exception */ 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(); } /** * @return */ 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"; } /** * @param path * @return */ 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(); } /** * @param path * @return */ 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(); } /** * @return */ private String createToken () { return Long.toString(random.nextLong()^System.currentTimeMillis(), 36).toUpperCase(Locale.ENGLISH); } /** * @param mode * @param inputStream */ 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(); } /** * @param strings * @return */ private String toCSV (List strings) { if (strings == null) return ""; StringBuffer strbuff = new StringBuffer(); Iterator itor = strings.iterator(); while (itor.hasNext()) { strbuff.append(itor.next()); if (itor.hasNext()) strbuff.append(","); } return strbuff.toString(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy