org.wisdom.maven.mojos.InternalRunMojo Maven / Gradle / Ivy
Show all versions of wisdom-maven-plugin Show documentation
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* Licensed 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.
* #L%
*/
package org.wisdom.maven.mojos;
import org.apache.commons.exec.ProcessDestroyer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.shared.dependency.graph.DependencyGraphBuilder;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
import org.wisdom.maven.Watcher;
import org.wisdom.maven.pipeline.Pipeline;
import org.wisdom.maven.pipeline.Pipelines;
import org.wisdom.maven.pipeline.Watchers;
import org.wisdom.maven.utils.WisdomExecutor;
import java.io.File;
/**
* The mojo launching the Wisdom server instance.
* Do not use, this mojo is invoked automatically from {@code mvn wisdom:run}.
*/
@Mojo(name = "internal-run", threadSafe = false,
// We need to use the TEST scope to let Surefire access its dependencies.
requiresDependencyResolution = ResolutionScope.TEST,
requiresProject = true
)
public class InternalRunMojo extends AbstractWisdomMojo implements Contextualizable {
private Pipeline pipeline;
/**
* If set to {@literal true}, it does not collect transitive dependencies. This means that bundles that are
* transitive dependencies of the current project won't be copied.
*/
@Parameter(defaultValue = "false")
private boolean excludeTransitive;
/**
* If set to {@literal false} (default), it enables the analysis and the collection of transitive webjars.
*/
@Parameter(defaultValue = "false")
private boolean excludeTransitiveWebJars;
/**
* Sets the debug port on which the remote debugger can be plugged.
* If set to 0 the debug is disabled (default).
*/
@Parameter(defaultValue = "${debug}")
public int debug;
/**
* JVM arguments appended to the Java Command.
* Arguments are given like: {@code mvn clean wisdom:run -DjvmArgs="-ea -Dfoo=bar"}
*/
@Parameter(defaultValue = "${jvmArgs}")
public String jvmArgs;
/**
* Enables the interactive mode of the launched server (shell prompt).
* Be ware that exiting the framework must be done using the 'exit' command instead of 'CTRL+C'.
*/
@Parameter(defaultValue = "${interactive}")
private boolean interactive;
/**
* Enables the interactive mode of the launched server (shell prompt). This option is equivalent to {@literal
* interactive}. Be ware that exiting the framework must be done using the 'exit' command instead of 'CTRL+C'.
*/
@Parameter(defaultValue = "${shell}")
private boolean shell;
/**
* Enables / disables the pom file monitoring.
*/
@Parameter(defaultValue = "${pom.watching}")
private boolean pomFileMonitoring = true; //NOSONAR Cannot be a local variable, it's injected.
/**
* A parameter indicating that the current project is using the 'base runtime' instead of the 'full runtime'. This
* option should only be used by components developed by Wisdom and being part of the 'full runtime'.
*
* This parameter is deprecated, used the "distribution" parameter instead.
*/
@Parameter
@Deprecated
boolean useBaseRuntime;
/**
* A parameter to select the Wisdom distribution to use. Are accepted a profile's name among: {base, equinox,
* regular} (regular is the default distribution), or artifact coordinated given under the form:
* GROUP_ID:ARTIFACT_ID:EXTENSION:CLASSIFIER:VERSION. If not set the regular distribution is used.
*/
@Parameter(defaultValue = "regular")
String wisdomRuntime;
/**
* The dependency graph builder to use.
*/
@Component(hint = "default")
private DependencyGraphBuilder dependencyGraphBuilder;
/**
* A parameter indicating whether or not we should remove from the bundle transitive copy some well-known
* error-prone bundles.
*/
@Parameter(defaultValue = "true")
public boolean useDefaultExclusions;
/**
* Configures the behavior of non-OSGi dependencies.
*/
@Parameter
public Libraries libraries;
//TODO Why do we have this field ?
private PlexusContainer container;
private ProcessController destroyer = new ProcessController();
/**
* This method is called when the JVM is shutting down and notify all the waiting threads.
*/
private void unblock() {
synchronized (this) {
// This notify can be considered as 'naked', because it does not modify the current object state (the
// object on which the synchronized is done). However in this very case,
// it's normal. The wait-notify protocol is only used to block the thread until the JVM stops.
notifyAll(); //NOSONAR
}
}
/**
* Execute method, initializes the watch pipeline. If the {@link #wisdomDirectory}
* parameter is set then the execution of the wisdom server is skipped,
* and keeps the thread alive until the shutdown of the JVM. Otherwise the wisdom server is
* started. Before returning, the method cleans up the pipeline.
*
* @throws org.apache.maven.plugin.MojoExecutionException for init failures.
*/
@Override
public void execute() throws MojoExecutionException {
try {
init();
} catch (ContextException e) {
throw new MojoExecutionException("Cannot extract the watchers from the context", e);
}
if (wisdomDirectory != null) {
getLog().info("Wisdom Directory set to " + wisdomDirectory.getAbsolutePath() + " - " +
"skipping the execution of the wisdom server for " + project.getArtifactId());
// Here things are a bit tricky. As we are not going to start the Wisdom server,
// the current thread is going to continue. We need to hold it and release it when
// the JVM stops. For this purpose we register a shutdown hook that is going to
// release the current thread we put in the waiting queue. This synchronization
// protocol is quite safe, as only one thread can enter this block.
// Register a shutdown hook that will unblock the execution when called.
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
/**
* Calls the unblock method.
*/
@Override
public void run() {
unblock();
}
}));
/**
* Entering a blocking block.
* We are going to wait until the JVM shuts down.
*/
synchronized (this) {
try {
// Here again, it's an unconditional wait. There are no condition on which we are waiting,
// we just hold the current thread until the JVM stops.
wait(); //NOSONAR
} catch (InterruptedException e) {
getLog().warn("We were interrupted", e);
}
}
} else {
new WisdomExecutor().execute(this, shell || interactive, debug, jvmArgs, destroyer);
}
pipeline.shutdown();
}
/**
* Init method, expands the Wisdom Runtime zip and copies compile dependencies to the
* application directory. If the {@link #wisdomDirectory} parameter is set then the expansion
* of the zip is skipped.
*
* @throws org.apache.maven.plugin.MojoExecutionException if copy of dependencies fails.
*/
public void init() throws MojoExecutionException, ContextException {
if (pomFileMonitoring) {
Watchers.add(session, new PomWatcher());
}
pipeline = Pipelines.watchers(session, basedir, this, pomFileMonitoring).watch();
}
/**
* The watcher responsible of stopping everything when the pom.xml file is changed.
*/
private class PomWatcher implements Watcher {
/**
* Checks that the given file is actually the 'pom.xml' file of the watched project.
*
* @param file is the file.
* @return {@literal true} if the watcher is interested in being notified on an event
* attached to the given file, {@literal false} otherwise.
*/
@Override
public boolean accept(File file) {
return file.equals(new File(project.getBasedir(), "pom.xml"));
}
/**
* Does nothing.
*
* @param file is the file.
* @return {@code true}
*/
@Override
public boolean fileCreated(File file) {
// Ignored event.
return true;
}
/**
* The 'pom.xml' file was updated.
*
* It shutdowns the pipeline, kill the underlying Chameleon instance and exit the JVM with a special exit
* code (20) instructing the parent process to relaunch Maven.
*
* @param file is the file.
* @return {@literal false}
*/
@Override
public boolean fileUpdated(File file) {
getLog().warn("A change in the 'pom.xml' has been detected, in order to take such a change into account, " +
"the Maven process is going to restart automatically. Don't forget to replug your debugger if you" +
" are debugging the application.");
pipeline.shutdown();
destroyer.stop();
return false;
}
/**
* Does nothing.
*
* @param file the file
* @return {@literal true}
*/
@Override
public boolean fileDeleted(File file) {
// Ignored event.
return true;
}
}
@Override
public void contextualize(Context context) throws ContextException {
container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY);
}
private class ProcessController implements ProcessDestroyer {
Process process;
@Override
public boolean add(Process process) {
if (this.process == null) {
this.process = process;
return true;
}
return false;
}
@Override
public boolean remove(Process process) {
if (this.process == process) {
this.process = null;
return true;
}
return false;
}
@Override
public int size() {
if (process != null) {
return 1;
}
return 0;
}
public int stop() {
if (process == null) {
return 0;
}
try {
process.destroy();
process.waitFor();
} catch (InterruptedException e) { //NOSONAR
// Ignore it.
}
return process.exitValue();
}
}
}