Polish start stop support

This commit is contained in:
Phillip Webb
2015-06-03 22:13:22 -07:00
parent 09a29a7207
commit bce4bb8860
13 changed files with 276 additions and 234 deletions

View File

@@ -22,8 +22,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName;
/**
* This sample app simulates the JMX Mbean that is exposed by the Spring
* Boot application.
* This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
*/
public class SampleApplication {
@@ -31,7 +30,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
ObjectName name = new ObjectName(
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name);
@@ -41,7 +41,8 @@ public class SampleApplication {
int waitAttempts = 0;
while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) {
throw new IllegalStateException("Shutdown should have been invoked by now");
throw new IllegalStateException(
"Shutdown should have been invoked by now");
}
synchronized (lock) {
lock.wait(250);
@@ -50,7 +51,6 @@ public class SampleApplication {
}
}
public interface SpringApplicationLifecycleMXBean {
boolean isReady();
@@ -76,5 +76,7 @@ public class SampleApplication {
this.shutdownInvoked = true;
System.out.println("Shutdown requested");
}
}
}
}

View File

@@ -21,8 +21,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName;
/**
* This sample app simulates the JMX Mbean that is exposed by the Spring
* Boot application.
* This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
*/
public class SampleApplication {
@@ -30,7 +29,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
ObjectName name = new ObjectName("org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
ObjectName name = new ObjectName(
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name);
@@ -40,7 +40,8 @@ public class SampleApplication {
int waitAttempts = 0;
while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) {
throw new IllegalStateException("Shutdown should have been invoked by now");
throw new IllegalStateException(
"Shutdown should have been invoked by now");
}
synchronized (lock) {
lock.wait(250);
@@ -49,7 +50,6 @@ public class SampleApplication {
}
}
public interface SpringApplicationLifecycleMXBean {
boolean isReady();
@@ -75,5 +75,7 @@ public class SampleApplication {
this.shutdownInvoked = true;
System.out.println("Shutdown requested");
}
}
}
}

View File

@@ -36,7 +36,6 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.MainClassFinder;
@@ -137,8 +136,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @return {@code true} if the application process should be forked
*/
protected boolean isFork() {
return (Boolean.TRUE.equals(this.fork)
|| (this.fork == null && (hasAgent() || hasJvmArgs())));
return (Boolean.TRUE.equals(this.fork) || (this.fork == null && (hasAgent() || hasJvmArgs())));
}
private boolean hasAgent() {
@@ -165,7 +163,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
}
CodeSource source = loaded.getProtectionDomain().getCodeSource();
if (source != null) {
this.agent = new File[] {new File(source.getLocation().getFile())};
this.agent = new File[] { new File(source.getLocation().getFile()) };
}
}
}
@@ -178,7 +176,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
}
}
private void run(String startClassName) throws MojoExecutionException, MojoFailureException {
private void run(String startClassName) throws MojoExecutionException,
MojoFailureException {
findAgent();
if (isFork()) {
doRunWithForkedJvm(startClassName);
@@ -196,8 +195,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
}
}
private void doRunWithForkedJvm(String startClassName)
throws MojoExecutionException, MojoFailureException {
private void doRunWithForkedJvm(String startClassName) throws MojoExecutionException,
MojoFailureException {
List<String> args = new ArrayList<String>();
addAgents(args);
addJvmArgs(args);
@@ -213,8 +212,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @throws MojoExecutionException
* @throws MojoFailureException
*/
protected abstract void runWithForkedJvm(List<String> args) throws MojoExecutionException, MojoFailureException;
protected abstract void runWithForkedJvm(List<String> args)
throws MojoExecutionException, MojoFailureException;
/**
* Run with the current VM, using the specified arguments.
@@ -277,8 +276,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
args.add("-cp");
args.add(classpath.toString());
}
catch (Exception e) {
throw new MojoExecutionException("Could not build classpath", e);
catch (Exception ex) {
throw new MojoExecutionException("Could not build classpath", ex);
}
}
@@ -358,8 +357,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
getLog().debug(sb.toString().trim());
}
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
public TestArtifactFilter() {
super("", Artifact.SCOPE_TEST);
}
@@ -368,6 +367,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
protected String getArtifactFeature(Artifact artifact) {
return artifact.getScope();
}
}
/**
@@ -423,7 +423,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
if (!mainMethod.isAccessible()) {
mainMethod.setAccessible(true);
}
mainMethod.invoke(null, new Object[] {this.args});
mainMethod.invoke(null, new Object[] { this.args });
}
catch (NoSuchMethodException ex) {
Exception wrappedEx = new Exception(

View File

@@ -42,7 +42,7 @@ class RunArguments {
}
public LinkedList<String> getArgs() {
return args;
return this.args;
}
public String[] asArray() {

View File

@@ -24,7 +24,6 @@ import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.RunProcess;
@@ -41,15 +40,17 @@ public class RunMojo extends AbstractRunMojo {
@Override
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException {
try {
new RunProcess(new JavaExecutable().toString()).run(true, args
.toArray(new String[args.size()]));
new RunProcess(new JavaExecutable().toString()).run(true,
args.toArray(new String[args.size()]));
}
catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex);
}
}
protected void runWithMavenJvm(String startClassName, String... arguments) throws MojoExecutionException {
@Override
protected void runWithMavenJvm(String startClassName, String... arguments)
throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
arguments), startClassName + ".main()");

View File

@@ -17,6 +17,7 @@
package org.springframework.boot.maven;
import java.io.IOException;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
@@ -35,20 +36,19 @@ import org.apache.maven.plugin.MojoExecutionException;
* information about the lifecycle of a given Spring application.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
class SpringApplicationLifecycleClient {
//Note: see org.springframework.boot.autoconfigure.test.SpringApplicationLifecycleAutoConfiguration
static final String DEFAULT_OBJECT_NAME =
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
// Note: see SpringApplicationLifecycleAutoConfiguration
static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
private final MBeanServerConnection mBeanServerConnection;
private final MBeanServerConnection connection;
private final ObjectName objectName;
public SpringApplicationLifecycleClient(MBeanServerConnection mBeanServerConnection, String jmxName) {
this.mBeanServerConnection = mBeanServerConnection;
public SpringApplicationLifecycleClient(MBeanServerConnection connection,
String jmxName) {
this.connection = connection;
this.objectName = toObjectName(jmxName);
}
@@ -66,30 +66,32 @@ class SpringApplicationLifecycleClient {
}
/**
* Check if the spring application managed by this instance is ready.
* <p>Returns {@code false} if the mbean is not yet deployed so this method
* should be repeatedly called until a timeout is reached.
* Check if the spring application managed by this instance is ready. Returns
* {@code false} if the mbean is not yet deployed so this method should be repeatedly
* called until a timeout is reached.
* @return {@code true} if the application is ready to service requests
* @throws MojoExecutionException if the JMX service could not be contacted
*/
public boolean isReady() throws MojoExecutionException {
try {
return (Boolean) this.mBeanServerConnection.getAttribute(this.objectName, "Ready");
return (Boolean) this.connection.getAttribute(this.objectName, "Ready");
}
catch (InstanceNotFoundException e) {
catch (InstanceNotFoundException ex) {
return false; // Instance not available yet
}
catch (AttributeNotFoundException e) {
throw new IllegalStateException("Unexpected: attribute 'Ready' not available", e);
catch (AttributeNotFoundException ex) {
throw new IllegalStateException(
"Unexpected: attribute 'Ready' not available", ex);
}
catch (ReflectionException e) {
throw new MojoExecutionException("Failed to retrieve Ready attribute", e.getCause());
catch (ReflectionException ex) {
throw new MojoExecutionException("Failed to retrieve Ready attribute",
ex.getCause());
}
catch (MBeanException e) {
throw new MojoExecutionException(e.getMessage(), e);
catch (MBeanException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
catch (IOException e) {
throw new MojoExecutionException(e.getMessage(), e);
catch (IOException ex) {
throw new MojoExecutionException(ex.getMessage(), ex);
}
}
@@ -99,15 +101,16 @@ class SpringApplicationLifecycleClient {
* @throws IOException if an I/O error occurs
* @throws InstanceNotFoundException if the lifecycle mbean cannot be found
*/
public void stop() throws MojoExecutionException, IOException, InstanceNotFoundException {
public void stop() throws MojoExecutionException, IOException,
InstanceNotFoundException {
try {
this.mBeanServerConnection.invoke(this.objectName, "shutdown", null, null);
this.connection.invoke(this.objectName, "shutdown", null, null);
}
catch (ReflectionException e) {
throw new MojoExecutionException("Shutdown failed", e.getCause());
catch (ReflectionException ex) {
throw new MojoExecutionException("Shutdown failed", ex.getCause());
}
catch (MBeanException e) {
throw new MojoExecutionException("Could not invoke shutdown operation", e);
catch (MBeanException ex) {
throw new MojoExecutionException("Could not invoke shutdown operation", ex);
}
}

View File

@@ -22,6 +22,8 @@ import java.net.ConnectException;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.management.MBeanServerConnection;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
@@ -32,15 +34,14 @@ import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.RunProcess;
/**
* Start a spring application. Contrary to the {@code run} goal, this does not
* block and allows other goal to operate on the application. This goal is typically
* used in integration test scenario where the application is started before a test
* suite and stopped after.
* Start a spring application. Contrary to the {@code run} goal, this does not block and
* allows other goal to operate on the application. This goal is typically used in
* integration test scenario where the application is started before a test suite and
* stopped after.
*
* @author Stephane Nicoll
* @since 1.3.0
@@ -54,30 +55,30 @@ public class StartMojo extends AbstractRunMojo {
private static final String JMX_NAME_PROPERTY_PREFIX = "--spring.context.lifecycle.jmx-name=";
/**
* The JMX name of the automatically deployed MBean managing the lifecycle
* of the spring application.
* The JMX name of the automatically deployed MBean managing the lifecycle of the
* spring application.
*/
@Parameter
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
/**
* The port to use to expose the platform MBeanServer if the application
* needs to be forked.
* The port to use to expose the platform MBeanServer if the application needs to be
* forked.
*/
@Parameter
private int jmxPort = 9001;
/**
* The number of milli-seconds to wait between each attempt to check if the
* spring application is ready.
* The number of milli-seconds to wait between each attempt to check if the spring
* application is ready.
*/
@Parameter
private long wait = 500;
/**
* The maximum number of attempts to check if the spring application is
* ready. Combined with the "wait" argument, this gives a global timeout
* value (30 sec by default)
* The maximum number of attempts to check if the spring application is ready.
* Combined with the "wait" argument, this gives a global timeout value (30 sec by
* default)
*/
@Parameter
private int maxAttempts = 60;
@@ -85,26 +86,30 @@ public class StartMojo extends AbstractRunMojo {
private final Object lock = new Object();
@Override
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException, MojoFailureException {
RunProcess runProcess;
try {
runProcess = new RunProcess(new JavaExecutable().toString());
runProcess.run(false, args.toArray(new String[args.size()]));
}
catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex);
}
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException,
MojoFailureException {
RunProcess runProcess = runProcess(args);
try {
waitForSpringApplication();
}
catch (MojoExecutionException e) {
catch (MojoExecutionException ex) {
runProcess.kill();
throw e;
throw ex;
}
catch (MojoFailureException e) {
catch (MojoFailureException ex) {
runProcess.kill();
throw e;
throw ex;
}
}
private RunProcess runProcess(List<String> args) throws MojoExecutionException {
try {
RunProcess runProcess = new RunProcess(new JavaExecutable().toString());
runProcess.run(false, args.toArray(new String[args.size()]));
return runProcess;
}
catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", ex);
}
}
@@ -113,7 +118,8 @@ public class StartMojo extends AbstractRunMojo {
RunArguments applicationArguments = super.resolveApplicationArguments();
applicationArguments.getArgs().addLast(ENABLE_MBEAN_PROPERTY);
if (isFork()) {
applicationArguments.getArgs().addLast(JMX_NAME_PROPERTY_PREFIX + this.jmxName);
applicationArguments.getArgs().addLast(
JMX_NAME_PROPERTY_PREFIX + this.jmxName);
}
return applicationArguments;
}
@@ -124,7 +130,7 @@ public class StartMojo extends AbstractRunMojo {
if (isFork()) {
List<String> remoteJmxArguments = new ArrayList<String>();
remoteJmxArguments.add("-Dcom.sun.management.jmxremote");
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.port=" + this.jmxPort);
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.authenticate=false");
remoteJmxArguments.add("-Dcom.sun.management.jmxremote.ssl=false");
jvmArguments.getArgs().addAll(remoteJmxArguments);
@@ -132,18 +138,19 @@ public class StartMojo extends AbstractRunMojo {
return jvmArguments;
}
protected void runWithMavenJvm(String startClassName, String... arguments) throws MojoExecutionException {
@Override
protected void runWithMavenJvm(String startClassName, String... arguments)
throws MojoExecutionException {
IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
arguments), startClassName + ".main()");
launchThread.setContextClassLoader(new URLClassLoader(getClassPathUrls()));
launchThread.start();
waitForSpringApplication(this.wait, this.maxAttempts);
}
private void waitForSpringApplication(long wait, int maxAttempts) throws MojoExecutionException {
private void waitForSpringApplication(long wait, int maxAttempts)
throws MojoExecutionException {
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(
ManagementFactory.getPlatformMBeanServer(), this.jmxName);
getLog().debug("Waiting for spring application to start...");
@@ -151,21 +158,26 @@ public class StartMojo extends AbstractRunMojo {
if (helper.isReady()) {
return;
}
getLog().debug("Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")");
String message = "Spring application is not ready yet, waiting " + wait
+ "ms (attempt " + (i + 1) + ")";
getLog().debug(message);
synchronized (this.lock) {
try {
this.lock.wait(wait);
}
catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start.");
catch (InterruptedException ex) {
throw new IllegalStateException(
"Interrupted while waiting for Spring Boot app to start.");
}
}
}
throw new MojoExecutionException("Spring application did not start before the configured " +
"timeout (" + (wait * maxAttempts) + "ms");
throw new MojoExecutionException(
"Spring application did not start before the configured timeout ("
+ (wait * maxAttempts) + "ms");
}
private void waitForSpringApplication() throws MojoFailureException, MojoExecutionException {
private void waitForSpringApplication() throws MojoFailureException,
MojoExecutionException {
try {
if (Boolean.TRUE.equals(isFork())) {
waitForForkedSpringApplication();
@@ -174,114 +186,133 @@ public class StartMojo extends AbstractRunMojo {
doWaitForSpringApplication(ManagementFactory.getPlatformMBeanServer());
}
}
catch (IOException e) {
throw new MojoFailureException("Could not contact Spring Boot application", e);
catch (IOException ex) {
throw new MojoFailureException("Could not contact Spring Boot application",
ex);
}
catch (Exception e) {
throw new MojoExecutionException("Could not figure out if the application has started", e);
catch (Exception ex) {
throw new MojoExecutionException(
"Could not figure out if the application has started", ex);
}
}
private void waitForForkedSpringApplication() throws IOException, MojoFailureException, MojoExecutionException {
final JMXConnector jmxConnector;
private void waitForForkedSpringApplication() throws IOException,
MojoFailureException, MojoExecutionException {
try {
getLog().debug("Connecting to local MBeanServer at port " + this.jmxPort);
jmxConnector = execute(wait, maxAttempts, new RetryCallback<JMXConnector>() {
@Override
public JMXConnector retry() throws Exception {
try {
return SpringApplicationLifecycleClient.createLocalJmxConnector(jmxPort);
}
catch (IOException e) {
if (hasCauseWithType(e, ConnectException.class)) { // Not there yet
getLog().debug("MBean server at port " + jmxPort + " is not up yet...");
return null;
}
else {
throw e;
}
}
}
});
if (jmxConnector == null) {
throw new MojoExecutionException("JMX MBean server was not reachable before the configured " +
"timeout (" + (this.wait * this.maxAttempts) + "ms");
JMXConnector connector = execute(this.wait, this.maxAttempts,
new CreateJmxConnector(this.jmxPort));
if (connector == null) {
throw new MojoExecutionException(
"JMX MBean server was not reachable before the configured "
+ "timeout (" + (this.wait * this.maxAttempts) + "ms");
}
getLog().debug("Connected to local MBeanServer at port " + this.jmxPort);
try {
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
doWaitForSpringApplication(mBeanServerConnection);
MBeanServerConnection connection = connector.getMBeanServerConnection();
doWaitForSpringApplication(connection);
}
finally {
jmxConnector.close();
connector.close();
}
}
catch (IOException e) {
throw e;
catch (IOException ex) {
throw ex;
}
catch (Exception e) {
throw new MojoExecutionException("Failed to connect to MBean server at port " + this.jmxPort, e);
catch (Exception ex) {
throw new MojoExecutionException("Failed to connect to MBean server at port "
+ this.jmxPort, ex);
}
}
private void doWaitForSpringApplication(MBeanServerConnection connection)
throws IOException, MojoExecutionException, MojoFailureException {
final SpringApplicationLifecycleClient client =
new SpringApplicationLifecycleClient(connection, this.jmxName);
final SpringApplicationLifecycleClient client = new SpringApplicationLifecycleClient(
connection, this.jmxName);
try {
execute(this.wait, this.maxAttempts, new RetryCallback<Boolean>() {
execute(this.wait, this.maxAttempts, new Callable<Boolean>() {
@Override
public Boolean retry() throws Exception {
boolean ready = client.isReady();
// Wait until the app is ready
return (ready ? true : null);
public Boolean call() throws Exception {
return (client.isReady() ? true : null);
}
});
}
catch (ReflectionException e) {
throw new MojoExecutionException("Unable to retrieve Ready attribute", e.getCause());
catch (ReflectionException ex) {
throw new MojoExecutionException("Unable to retrieve 'ready' attribute",
ex.getCause());
}
catch (Exception e) {
throw new MojoFailureException("Could not invoke shutdown operation", e);
catch (Exception ex) {
throw new MojoFailureException("Could not invoke shutdown operation", ex);
}
}
public <T> T execute(long wait, int maxAttempts, RetryCallback<T> callback) throws Exception {
/**
* Execute a task, retrying it on failure.
* @param wait the wait time
* @param maxAttempts the maximum number of attempts
* @param callback the task to execute (possibly multiple times). The callback should
* return {@code null} to indicate that another attempt should be made
* @return the result
* @throws Exception
*/
public <T> T execute(long wait, int maxAttempts, Callable<T> callback)
throws Exception {
getLog().debug("Waiting for spring application to start...");
for (int i = 0; i < maxAttempts; i++) {
T result = callback.retry();
T result = callback.call();
if (result != null) {
return result;
}
getLog().debug("Spring application is not ready yet, waiting " + wait + "ms (attempt " + (i + 1) + ")");
String message = "Spring application is not ready yet, waiting " + wait
+ "ms (attempt " + (i + 1) + ")";
getLog().debug(message);
synchronized (this.lock) {
try {
this.lock.wait(wait);
}
catch (InterruptedException e) {
throw new IllegalStateException("Interrupted while waiting for Spring Boot app to start.");
catch (InterruptedException ex) {
throw new IllegalStateException(
"Interrupted while waiting for Spring Boot app to start.");
}
}
}
throw new MojoExecutionException("Spring application did not start before the configured " +
"timeout (" + (wait * maxAttempts) + "ms");
throw new MojoExecutionException(
"Spring application did not start before the configured " + "timeout ("
+ (wait * maxAttempts) + "ms");
}
private static boolean hasCauseWithType(Throwable t, Class<? extends Exception> type) {
return type.isAssignableFrom(t.getClass()) || t.getCause() != null && hasCauseWithType(t.getCause(), type);
}
private class CreateJmxConnector implements Callable<JMXConnector> {
private final int port;
interface RetryCallback<T> {
public CreateJmxConnector(int port) {
this.port = port;
}
@Override
public JMXConnector call() throws Exception {
try {
return SpringApplicationLifecycleClient
.createLocalJmxConnector(this.port);
}
catch (IOException ex) {
if (hasCauseWithType(ex, ConnectException.class)) {
String message = "MBean server at port " + this.port
+ " is not up yet...";
getLog().debug(message);
return null;
}
throw ex;
}
}
private boolean hasCauseWithType(Throwable t, Class<? extends Exception> type) {
return type.isAssignableFrom(t.getClass()) || t.getCause() != null
&& hasCauseWithType(t.getCause(), type);
}
/**
* Attempt to execute an operation. Throws an exception in case of fatal
* exception, returns {@code null} to indicate another attempt should be
* made if possible.
*/
T retry() throws Exception;
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.boot.maven;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
@@ -40,23 +41,23 @@ import org.apache.maven.plugins.annotations.Parameter;
public class StopMojo extends AbstractMojo {
/**
* Flag to indicate if the run processes should be forked. Must be aligned to the value
* used to {@link StartMojo start} the process
* Flag to indicate if the run processes should be forked. Must be aligned to the
* value used to {@link StartMojo start} the process
* @since 1.2
*/
@Parameter(property = "fork")
private Boolean fork;
/**
* The JMX name of the automatically deployed MBean managing the lifecycle
* of the application.
* The JMX name of the automatically deployed MBean managing the lifecycle of the
* application.
*/
@Parameter
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
/**
* The port to use to lookup the platform MBeanServer if the application
* has been forked.
* The port to use to lookup the platform MBeanServer if the application has been
* forked.
*/
@Parameter
private int jmxPort = 9001;
@@ -72,9 +73,22 @@ public class StopMojo extends AbstractMojo {
stop();
}
}
catch (IOException e) {
catch (IOException ex) {
// The response won't be received as the server has died - ignoring
getLog().debug("Service is not reachable anymore (" + e.getMessage() + ")");
getLog().debug("Service is not reachable anymore (" + ex.getMessage() + ")");
}
}
private void stopForkedProcess() throws IOException, MojoFailureException,
MojoExecutionException {
JMXConnector connector = SpringApplicationLifecycleClient
.createLocalJmxConnector(this.jmxPort);
try {
MBeanServerConnection connection = connector.getMBeanServerConnection();
doStop(connection);
}
finally {
connector.close();
}
}
@@ -82,26 +96,15 @@ public class StopMojo extends AbstractMojo {
doStop(ManagementFactory.getPlatformMBeanServer());
}
private void stopForkedProcess() throws IOException, MojoFailureException, MojoExecutionException {
JMXConnector jmxConnector = SpringApplicationLifecycleClient.createLocalJmxConnector(this.jmxPort);
private void doStop(MBeanServerConnection connection) throws IOException,
MojoExecutionException {
try {
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
doStop(mBeanServerConnection);
new SpringApplicationLifecycleClient(connection, this.jmxName).stop();
}
finally {
jmxConnector.close();
}
}
private void doStop(MBeanServerConnection connection)
throws IOException, MojoExecutionException {
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(connection, this.jmxName);
try {
helper.stop();
}
catch (InstanceNotFoundException e) {
throw new MojoExecutionException("Spring application lifecycle JMX bean not found (fork is " +
"" + this.fork + "). Could not stop application gracefully", e);
catch (InstanceNotFoundException ex) {
throw new MojoExecutionException(
"Spring application lifecycle JMX bean not found (fork is " + ""
+ this.fork + "). Could not stop application gracefully", ex);
}
}