Overview

This plugin contains utilities to help manipulate processes and external programs. It allows you to easily compile/package/install a Maven project located outside your application. It also provides a way to programmatically launch and control an executable .jar file.

Usage

The utilities provided by this plugin are available through the SpincastProcessUtils interface, which you can inject wherever you want.

Running a goal on an external Maven project

The executeGoalOnExternalMavenProject(...) method allows you to programmatically compile, package or install a Maven project located outside of your application.

This feature can be useful for example to run tests that need to validate code from inside a .jar file.

The signature of this method is:

public File executeGoalOnExternalMavenProject(ResourceInfo projectRootInfo,
                                              MavenProjectGoal mavenGoal,
                                              Map<String,Object> pomParams);

Using this method you can:

  • Run the compile, package or install goal on a Maven project located:
    • On the file system
    • On the classpath (it will first be extracted to a temporary directory on the file system).
  • Specify parameters to be used to replace some placeholders in the project's pom.xml, before executing the Maven goal. The replacement is done using the Templating Engine.

Note that this method returns the root directory of the Maven project. If the project is extracted from the classpath, this directory is going to be created under the Temporary Directory. If the project is already on the file system, it will return its root directory, as is.

Let's look at an example where we run the package goal on a Maven project located on the classpath (the project would be provided by your application at "src/main/resources/myMavenProject"):

String spincastVersion = getSpincastUtils().getSpincastCurrentVersion();

File projectDir = getSpincastProcessUtils()
        .executeGoalOnExternalMavenProject(new ResourceInfo("/myMavenProject", true),
                                           MavenProjectGoal.PACKAGE,
                                           SpincastStatics.map("spincastVersion",
                                                               spincastVersion));

File jarFile = new File(projectDir, 
                        "target/project-artifact-name-" + spincastVersion + ".jar");
assertTrue(jarFile.isFile());

Explanation :

  • 1 : We get the current Spincast version. We will use replace a placeholder in the project's pom.xml file with this version.
  • 4 : We call the executeGoalOnExternalMavenProject() method. The first parameter is the path to the Maven project. Here, the project is on the classpath ("true").
  • 5 : We specify the Maven goal to run. In this example, we will call the "package" goal so the .jar associated with this project is generated.
  • 6-7 : The parameters to be used to replace placeholders in the project's pom.xml file (the Templating Engine is used to do so).
  • 9-11: A simple assertion to show that the project has been packaged successfully and that the associated .jar file has been successfully generated!

Have a look at this test for a real example of running a goal on an external Maven project.

Running an external executable .jar file

The executeJar(...) method allows you to run an external executable .jar file and to keep control over the process. It starts the target .jar file using java -jar yourJar.jar arg1 arg2 arg3 ...

Here is the signature of the method:

public void executeJar(String jarFilePath,
                       List<String> args,
                       JarExecutionHandler handler)

Explanation :

  • 1 : The absolute path to the executable .jar file to run, on the file system.
  • 2 : Arguments to be passed to the .jar when executing it.
  • 3 : An handler allowing you to keep control over the started process.

The JarExecutionHandler instance you pass to the method allows you to control the process that is going to be started to run the .jar file. The default implementation is JarExecutionHandlerDefault and you can, of course, extend it.

The main methods of the handler are:

  • Process getJarProcess()

    To get direct access to the Process started to execute the .jar file.

  • void onException(Exception ex)

    This method is going to be called if an exception occurs during the launch of the executable .jar file. The default method, in JarExecutionHandlerDefault, will catch the exception, will make sure the process is killed properly, and will then rethrow the exception.

  • void onExit(int exitVal)

    This method will be called when the process exits. If it exits by itself, without any error, exitVal will in general be "0".

    Note that if you execute a .jar file that doesn't exit by itself (for example an HTTP server is started by the .jar file), you have to explicitly kill the process.

    The default implementation for this method simply logs the exit value.

  • void killJarProcess()

    Kills the .jar process.

    If it is not able to kill it, for any reason, an exception is thrown.

  • void waitForPortOpen(String host, int port, int nbrTry, int sleepMilliseconds)

    This method, provided by JarExecutionHandlerDefault, allows you to wait for a port to be available. This is useful when the executable .jar file starts an HTTP server and you need to wait for it to be ready.

    If the port is still not available after nbrTry * sleepMilliseconds, a PortNotOpenException exception is thrown.

You can create a custom implementation of JarExecutionHandler to be used as the handler. This gives you control over how errors and exit codes are dealt with. Your implementation can extend the default JarExecutionHandlerDefault implementation.

If you need to specify the full path to the java binary to be used to execute the .jar file ("/usr/bin/java" for example), you can do so using this overload. Otherwise, "java" is used, as is, and must be on your PATH.

Finally, note that if you run an executable .jar file that doesn't exit by itself (for example it starts an HTTP server that listens forever for requests), you are responsible to kill the process when you are done with it, by calling killJarProcess(). This can be done using a try/finally block:

JarExecutionHandlerDefault handler = new JarExecutionHandlerDefault();

// Start an executable jar on port 12345
getSpincastProcessUtils().executeJar("/some/path.jar", 
                                     Lists.newArrayList("12345")),
                                     handler);
try {
    // Wait for the port 12345 to be open
    handler.waitForPortOpen("localhost", 12345, 5, 1000);

    // Make a request to the HTTP server started by the .jar file
    HttpResponse response = getHttpClient().GET("http://localhost:12345").send();
    assertEquals(HttpStatus.SC_OK, response.getStatus());
    // ...
} finally {
    // Kill the process when we are done with it!
    handler.killJarProcess();
}

Installation

1. Add this Maven artifact to your project:

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-process-utils</artifactId>
    <version>1.7.3</version>
</dependency>

2. Add an instance of the SpincastProcessUtilsPlugin plugin to your Spincast Bootstrapper:


Spincast.configure()
        .plugin(new SpincastProcessUtilsPlugin())
        // ...