templates.plugins.spincast-process-utils.spincast-process-utils.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{% extends "../../layout.html" %}
{% block sectionClasses %}plugins hasBreadCrumb plugins-spincast-process-utils{% endblock %}
{% block meta_title %}Plugins - Spincast Process Utils{% endblock %}
{% block meta_description %}Spincast Process Utils plugin - utilities related to processes and external programs manipulation{% endblock %}
{% block scripts %}
{% endblock %}
{% block body %}
Overview
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.
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:
{% verbatim %}
public File executeGoalOnExternalMavenProject(ResourceInfo projectRootInfo,
MavenProjectGoal mavenGoal,
Map<String,Object> pomParams);
{% endverbatim %}
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"):
{% verbatim %}
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());
{% endverbatim %}
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.
Executing an external program, asynchronously
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:
{% verbatim %}
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();
}
{% endverbatim %}
Explanation :
-
1-6 : The
ProcessExecutionHandler
handler that is going to receive information
about the created process. Here, we base this handler on the default
ProcessExecutionHandlerDefault
implementation and we only override
onSystemOut(...)
.
-
9 : We call the executeAsync(...)
method.
-
10 : The program to run. Here, we will use
java
in order
to start an executable .jar file. Note that you can (and sometimes must) specify an absolute path.
-
11-13 : The program arguments. Here, the resulting command would be:
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!).
-
16 : The default
ProcessExecutionHandlerDefault handler
provides a waitForPortOpen(...)
method which allows you to wait for a port to be open (this method is synchronous!).
-
19-20 : You use the started program. In our example, the executed
.jar file started an HTTP server on port
12345
. We make a request to it.
-
22-25 : When we are done with the programmatically started program,
we make sure its process is killed by calling
killProcess().
Doing so is not always required but it is when the executed program does not exit by itself
(as in our example, since the HTTP server started by the jar would listen for requests indefinitely).
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.
Executing an external program, synchronously
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:
{% verbatim %}
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
}
{% endverbatim %}
Explanation :
-
2 :
A SyncExecutionResult
object will be returned and will contain the result of the execution.
-
3 : We call
executeSync(...)
to launch the program synchronously.
-
3-4 : A timeout is always required when executing a program
synchronously!
-
5 : The strategy to use to deal with the output from the
created process. Here, we ask for the outputs (
Standard output
and Standard errors
)
to be buffered so they are available through the SyncExecutionResult
result object when the execution is done.
-
6 : The program to execute.
-
7-8 : arguments for the program.
-
9 : as soon as the
executeSync(...)
method
exits, we can get information from the SyncExecutionResult
object. Here, we
simply validate that the program exited with the code "0
".
-
11-12 : We manage the exception if one occurs
during the launch of the program.
-
14-16 : We manage the timeout exception,
if any.
Two typed exceptions can be thrown when using executeSync()
:
-
LaunchException:
if an error occurs during the launch of the program.
-
TimeoutException:
if the program launched successfully but timed out. Note that the process
will then be killed automatically.
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 (default): the
output will be redirected to
System.out
or System.err
immediately, as it occurs.
-
BUFFER: the
output will be buffered so it is available through the
getSystemOutLines()
and getSystemErrLines()
methods of the
SyncExecutionResult
result object.
-
NONE: the output
is ignored and discarded.
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.
Installation
1.
Add this Maven artifact to your project:
<dependency>
<groupId>org.spincast</groupId>
<artifactId>spincast-plugins-process-utils</artifactId>
<version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>
2. Add an instance of the SpincastProcessUtilsPlugin
plugin to your Spincast Bootstrapper:
{% verbatim %}
Spincast.configure()
.plugin(new SpincastProcessUtilsPlugin())
// ...
{% endverbatim %}
{% endblock %}