/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.mojo.pluginsupport.ant; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.Timer; import java.util.TimerTask; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.apache.tools.ant.taskdefs.Java; import org.apache.tools.ant.types.Path; import org.codehaus.plexus.util.FileUtils; import org.codehaus.mojo.pluginsupport.util.ObjectHolder; // // FIXME: Need to find a better way to allow plugins to re-use the parameter configuration! // /** * Support for mojos that launch Java processes. * * @version $Id$ */ public abstract class JavaLauncherMojoSupport extends AntMojoSupport { // // TODO: Use AntHelper component and extend from MojoSupport // private Timer timer = new Timer(true); /** * Set the maximum memory for the forked JVM. * * @parameter expression="${maximumMemory}" */ private String maximumMemory = null; /** * The base working directory where process will be started from, a sub-directory * the process name will be used for the effective working directory. * * @parameter expression="${project.build.directory}" * @required */ protected File baseWorkingDirectory = null; /** * Enable logging mode. * * @parameter expression="${logOutput}" default-value="false" */ protected boolean logOutput = false; /** * Flag to control if we background the process or block Maven execution. * * @parameter default-value="false" * @required */ protected boolean background = false; /** * Timeout for the process in seconds. * * @parameter expression="${timeout}" default-value="-1" */ protected int timeout = -1; /** * Time in seconds to wait while verifing that the process has started (if there is custom validation). * * @parameter expression="${verifyTimeout}" default-value="-1" */ private int verifyTimeout = -1; /** * An array of option sets which can be enabled by setting <tt>options</tt>. * * @parameter */ protected OptionSet[] optionSets = null; /** * A comma seperated list of <tt>optionSets</tt> to enabled. * * @parameter expression="${options}" */ protected String options = null; /** * Map of of plugin artifacts. * * @parameter expression="${plugin.artifactMap}" * @required * @readonly */ protected Map pluginArtifactMap = null; protected void doExecute() throws Exception { log.info("Starting " + getProcessTitle() + "..."); final Java java = (Java)createTask("java"); File workingDirectory = getWorkingDirectory(); FileUtils.forceMkdir(workingDirectory); java.setDir(workingDirectory); java.setFailonerror(true); java.setFork(true); if (maximumMemory != null) { java.setMaxmemory(maximumMemory); } if (timeout > 0) { log.info("Timeout after: " + timeout + " seconds"); java.setTimeout(new Long(timeout * 1000)); } if (logOutput) { File file = getLogFile(); log.info("Redirecting output to: " + file); FileUtils.forceMkdir(file.getParentFile()); java.setLogError(true); java.setOutput(file); } java.setClassname(getClassName()); setClassPath(java.createClasspath()); applyOptionSets(java); customizeJava(java); // Holds any exception that was thrown during startup final ObjectHolder errorHolder = new ObjectHolder(); // Start the process in a seperate thread Thread t = new Thread(getProcessTitle() + " Runner") { public void run() { try { java.execute(); } catch (Exception e) { errorHolder.set(e); // // NOTE: Don't log here, as when the JVM exists an exception will get thrown by Ant // but that should be fine. // } } }; t.start(); log.debug("Waiting for " + getProcessTitle() + "..."); // Setup a callback to time out verification final ObjectHolder verifyTimedOut = new ObjectHolder(); TimerTask timeoutTask = new TimerTask() { public void run() { verifyTimedOut.set(Boolean.TRUE); } }; if (verifyTimeout > 0) { log.debug("Starting verify timeout task; triggers in: " + verifyTimeout + "s"); timer.schedule(timeoutTask, verifyTimeout * 1000); } // Verify the process has started boolean started = false; while (!started) { if (verifyTimedOut.isSet()) { throw new MojoExecutionException("Unable to verify if the " + getProcessTitle() + " process was started in the given time"); } if (errorHolder.isSet()) { throw new MojoExecutionException("Failed to launch " + getProcessTitle(), (Throwable)errorHolder.get()); } try { started = verifyProcessStarted(); } catch (Exception e) { // ignore } Thread.sleep(1000); } log.info(getProcessTitle() + " started"); if (!background) { log.info("Waiting for " + getProcessTitle() + " to shutdown..."); t.join(); } } protected Artifact getPluginArtifact(final String name) throws MojoExecutionException { assert name != null; Artifact artifact = (Artifact)pluginArtifactMap.get(name); if (artifact == null) { throw new MojoExecutionException("Unable to locate '" + name + "' in the list of plugin artifacts"); } return artifact; } protected void appendArtifactFile(final Path classpath, final String name) throws MojoExecutionException { assert classpath != null; assert name != null; appendArtifact(classpath, getPluginArtifact(name)); } protected void appendArtifact(final Path classpath, final Artifact artifact) throws MojoExecutionException { assert classpath != null; assert artifact != null; File file = artifact.getFile(); if (file == null) { throw new MojoExecutionException("Artifact does not have an attached file: " + artifact); } classpath.createPathElement().setLocation(file); } private void applyOptionSets(final Java java) throws MojoExecutionException { assert java != null; // // TODO: Add optionSet activation // // Apply option sets if (options != null && (optionSets == null || optionSets.length == 0)) { throw new MojoExecutionException("At least one optionSet must be defined to select one using options"); } else if (options == null) { options = "default"; } if (optionSets != null && optionSets.length != 0) { OptionSet[] sets = selectOptionSets(); for (int i=0; i < sets.length; i++) { if (log.isDebugEnabled()) { log.debug("Selected option set: " + sets[i]); } else { log.info("Selected option set: " + sets[i].getId()); } String[] options = sets[i].getOptions(); if (options != null) { for (int j=0; j < options.length; j++) { java.createJvmarg().setValue(options[j]); } } Properties props = sets[i].getProperties(); if (props != null) { Iterator iter = props.keySet().iterator(); while (iter.hasNext()) { String name = (String)iter.next(); String value = props.getProperty(name); setSystemProperty(java, name, value); } } } } } private OptionSet[] selectOptionSets() throws MojoExecutionException { // Make a map of the option sets and validate ids Map map = new HashMap(); for (int i=0; i<optionSets.length; i++) { if (log.isDebugEnabled()) { log.debug("Checking option set: " + optionSets[i]); } String id = optionSets[i].getId(); if (id == null && optionSets.length > 1) { throw new MojoExecutionException("Must specify id for optionSet when more than one optionSet is configured"); } else if (id == null && optionSets.length == 1) { id = "default"; optionSets[i].setId(id); } assert id != null; id = id.trim(); if (map.containsKey(id)) { throw new MojoExecutionException("Must specify unique id for optionSet: " + optionSets[i]); } map.put(id, optionSets[i]); } StringTokenizer stok = new StringTokenizer(options, ","); List selected = new ArrayList(); while (stok.hasMoreTokens()) { String id = stok.nextToken(); OptionSet set = (OptionSet)map.get(options); if (set == null) { if ("default".equals(options)) { log.debug("Default optionSet selected, but no optionSet defined with that id; ignoring"); } else { throw new MojoExecutionException("Missing optionSet for id: " + id); } } else { selected.add(set); } } return (OptionSet[]) selected.toArray(new OptionSet[selected.size()]); } // // MojoSupport Hooks // /** * The maven project. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project = null; protected MavenProject getProject() { return project; } /** * @parameter expression="${localRepository}" * @readonly * @required */ protected ArtifactRepository artifactRepository = null; protected ArtifactRepository getArtifactRepository() { return artifactRepository; } // // Sub-class API // protected abstract String getProcessName(); protected String getProcessTitle() { return getProcessName(); } protected File getWorkingDirectory() { return new File(baseWorkingDirectory, getProcessName()); } protected File getLogFile() { return new File(getWorkingDirectory(), getProcessName() + ".log"); } protected abstract String getClassName(); protected abstract void setClassPath(Path classpath) throws Exception; protected void customizeJava(final Java java) throws MojoExecutionException { assert java != null; // nothing by default } protected boolean verifyProcessStarted() throws Exception { return true; } }