This plugin contains utilities to programmatically execute and
manipulate external programs.
It also allows you to easily compile/package/install a Maven
project
located outside your application.
The utilities provided by this plugin are available through the SpincastProcessUtils interface, which you can inject wherever you want.
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:
compile
, package
or install
goal on a Maven
project located:
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 :
pom.xml
file with this version.
executeGoalOnExternalMavenProject()
method.
The first parameter is the path to the Maven project. Here, the project is on the classpath ("true
").
goal
to run. In this example, we will call
the "package" goal so the .jar
associated with this project is generated.
pom.xml
file (the Templating Engine is used
to do so).
.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.
The executeAsync(...) method allows you to run an external program and to keep control over the created process.
Here is an example where we run an executable .jar
file programmatically:
ProcessExecutionHandlerDefault handler = new ProcessExecutionHandlerDefault() { @Override public void onSystemOut(String line) { System.out.println("[jar out] - " + line); } }; // Start an executable jar getSpincastProcessUtils().executeAsync(handler, "java", "-jar", "/some/path.jar", "12345"); // argument for the .jar program 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 program 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.killProcess(); }
Explanation :
onSystemOut(...)
.
java
in order
to start an executable .jar file. Note that you can (and sometimes must) specify an absolute path.
java -jar /some/path.jar 12345
. The "12345
" argument
would be passed to the executed jar (in our example this is the port to use
for the jar to start an HTTP server!).
12345
. We make a request to it.
On some environments, you may have to specify the full path to the program to be executed
(it may depend on the PATH
variable of your system).
Here are the methods available on the ProcessExecutionHandler handler and that need to be implemented (if you don't use the ProcessExecutionHandlerDefault default implementation, in which case you can override some):
void onExit(int exitCode)
This method will be called when the process exits. If it exits without any error,
exitCode
will in general be "0
".
The default implementation, from ProcessExecutionHandlerDefault, will simply log the exit code.
Note that if you execute a program that does not exit by itself (for example an HTTP server is started and wait for requests indefinitely), you have to explicitly kill the process.
The onEnd() method is called after this one.
void onLaunchException(Exception ex)
This method is going to be called if an exception occurs during the launch of the external program.
The default implementation, from ProcessExecutionHandlerDefault, will simply log the exception.
Note that when this method is called, onExit() will not be called: the program was never even started.
The onEnd() method is called after this one.
void onTimeoutException()
This method is going to be called if the program is launched properly but doesn't exit before the specified timeout is reached (if one is specified) .
The default implementation, from ProcessExecutionHandlerDefault, will simply log the error.
Note that when this method is called, onExit() will not be called.
The process is killed automatically.
The onEnd() method is called after this one.
void onEnd()
Always called at the end, whether the process exited properly or an exception/timeout occurred.
void onSystemOut(String line)
This method will be called when a line is printed to the standard output by the executed program.
The default implementation, from
ProcessExecutionHandlerDefault,
will simply log the line using System.out.println()
.
void onSystemErr(String line)
This method will be called when a line is printed to the standard errors output by the executed program.
The default implementation, from
ProcessExecutionHandlerDefault,
will simply log the line using System.err.println()
.
void killProcess()
Kills the created process.
If the process can't be killed, for any reason, an exception is thrown.
Only the first call to this method will be acknowledged, the following ones will simply be ignored.
boolean isProcessAlive()
Return true
if the created process is alive.
void waitForPortOpen(String host, int port, int nbrTry, int sleepMilliseconds)
This method, provided by the ProcessExecutionHandlerDefault
default implementation, allows you to wait for a port
to be connectable. This is useful if the
executed program starts an HTTP server and you need to wait for it to be ready.
If the port is still not available after nbrTry * sleepMilliseconds
milliseconds, a
PortNotOpenException
exception is thrown.
Remember that if you run a program that doesn't exit by itself (for example
it starts an HTTP server that listens for requests indefinitely), you are responsible
to kill the process when you are done with it, by calling killProcess()!
This can be done using a try/finally
block.
As we saw in the previous section, you can execute an external program asynchronously, so it doesn't block the current thread and can be manipulated. In simple cases, you can also execute it synchronously. Here is an example:
try { SyncExecutionResult result = getSpincastProcessUtils().executeSync(3, TimeUnit.MINUTES, ExecutionOutputStrategy.BUFFER, "/my/program/to/execute", "arg1", "arg2"); assertEquals(new Integer(0), result.getExitCode()); } catch (LaunchException ex) { // manage the exception } catch (TimeoutException ex) { // manage the exception }
Explanation :
Standard output
and Standard errors
)
to be buffered so they are available through the SyncExecutionResult
result object when the execution is done.
executeSync(...)
method
exits, we can get information from the SyncExecutionResult
object. Here, we
simply validate that the program exited with the code "0
".
Two typed exceptions can be thrown when using executeSync()
:
The methods available on the SyncExecutionResult object are:
int getExitCode()
The exit code of the executed program.
Note that this code is undefined if the program timed out and the
SyncExecutionResult
instance has been taken from
TimeoutException
's
getSyncExecutionResult()!
List<String> getSystemOutLines()
The lines outputted to the standard output by the created process. Those will only be available if the ExecutionOutputStrategy parameter is explicitly set to BUFFER. Otherwise, an empty list is returned.
List<String> getSystemErrLines()
The lines outputted to the standard errors by the created process. Those will only be available if the ExecutionOutputStrategy parameter is explicitly set to BUFFER. Otherwise, an empty list is returned.
The strategies that you can use to deal with the output of the created process are:
System.out
or System.err
immediately, as it occurs.
SyncExecutionResult
result object.
Beware that buffering the output (by using BUFFER as the strategy) may consume a lot of memory if the created process generates a lot of it! If this is the case for you, you may have to use the asynchronous method instead.
1. Add this Maven artifact to your project:
<dependency> <groupId>org.spincast</groupId> <artifactId>spincast-plugins-process-utils</artifactId> <version>2.2.0</version> </dependency>
2. Add an instance of the SpincastProcessUtilsPlugin plugin to your Spincast Bootstrapper:
Spincast.configure() .plugin(new SpincastProcessUtilsPlugin()) // ...