Commit bce4bb88 authored by Phillip Webb's avatar Phillip Webb

Polish start stop support

parent 09a29a72
...@@ -43,7 +43,8 @@ import org.springframework.jmx.export.MBeanExporter; ...@@ -43,7 +43,8 @@ import org.springframework.jmx.export.MBeanExporter;
class SpringApplicationLifecycleAutoConfiguration { class SpringApplicationLifecycleAutoConfiguration {
/** /**
* The property to use to customize the {@code ObjectName} of the application lifecycle mbean. * The property to use to customize the {@code ObjectName} of the application
* lifecycle mbean.
*/ */
static final String JMX_NAME_PROPERTY = "spring.context.lifecycle.jmx-name"; static final String JMX_NAME_PROPERTY = "spring.context.lifecycle.jmx-name";
...@@ -61,10 +62,10 @@ class SpringApplicationLifecycleAutoConfiguration { ...@@ -61,10 +62,10 @@ class SpringApplicationLifecycleAutoConfiguration {
@Bean @Bean
public SpringApplicationLifecycleRegistrar springApplicationLifecycleRegistrar() public SpringApplicationLifecycleRegistrar springApplicationLifecycleRegistrar()
throws MalformedObjectNameException { throws MalformedObjectNameException {
String jmxName = this.environment
String jmxName = this.environment.getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME); .getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME);
if (mbeanExporter != null) { // Make sure to not register that MBean twice if (this.mbeanExporter != null) { // Make sure to not register that MBean twice
mbeanExporter.addExcludedBean(jmxName); this.mbeanExporter.addExcludedBean(jmxName);
} }
return new SpringApplicationLifecycleRegistrar(jmxName); return new SpringApplicationLifecycleRegistrar(jmxName);
} }
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.context; package org.springframework.boot.autoconfigure.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
...@@ -28,7 +29,6 @@ import org.junit.Before; ...@@ -28,7 +29,6 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -65,17 +65,16 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -65,17 +65,16 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
} }
@Test @Test
public void notRegisteredByDefault() throws MalformedObjectNameException, InstanceNotFoundException { public void notRegisteredByDefault() throws MalformedObjectNameException,
InstanceNotFoundException {
load(); load();
this.thrown.expect(InstanceNotFoundException.class);
thrown.expect(InstanceNotFoundException.class);
this.mBeanServer.getObjectInstance(createDefaultObjectName()); this.mBeanServer.getObjectInstance(createDefaultObjectName());
} }
@Test @Test
public void registeredWithProperty() throws Exception { public void registeredWithProperty() throws Exception {
load(ENABLE_LIFECYCLE_PROP); load(ENABLE_LIFECYCLE_PROP);
ObjectName objectName = createDefaultObjectName(); ObjectName objectName = createDefaultObjectName();
ObjectInstance objectInstance = this.mBeanServer.getObjectInstance(objectName); ObjectInstance objectInstance = this.mBeanServer.getObjectInstance(objectName);
assertNotNull("Lifecycle bean should have been registered", objectInstance); assertNotNull("Lifecycle bean should have been registered", objectInstance);
...@@ -84,18 +83,17 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -84,18 +83,17 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
@Test @Test
public void registerWithCustomJmxName() throws InstanceNotFoundException { public void registerWithCustomJmxName() throws InstanceNotFoundException {
String customJmxName = "org.acme:name=FooBar"; String customJmxName = "org.acme:name=FooBar";
System.setProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY, customJmxName); System.setProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY,
customJmxName);
try { try {
load(ENABLE_LIFECYCLE_PROP); load(ENABLE_LIFECYCLE_PROP);
try { try {
this.mBeanServer.getObjectInstance(createObjectName(customJmxName)); this.mBeanServer.getObjectInstance(createObjectName(customJmxName));
} }
catch (InstanceNotFoundException e) { catch (InstanceNotFoundException ex) {
fail("lifecycle MBean should have been exposed with custom name"); fail("lifecycle MBean should have been exposed with custom name");
} }
this.thrown.expect(InstanceNotFoundException.class); // Should not be exposed
thrown.expect(InstanceNotFoundException.class); // Should not be exposed
this.mBeanServer.getObjectInstance(createDefaultObjectName()); this.mBeanServer.getObjectInstance(createDefaultObjectName());
} }
finally { finally {
...@@ -111,18 +109,18 @@ public class SpringApplicationLifecycleAutoConfigurationTests { ...@@ -111,18 +109,18 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
try { try {
return new ObjectName(jmxName); return new ObjectName(jmxName);
} }
catch (MalformedObjectNameException e) { catch (MalformedObjectNameException ex) {
throw new IllegalStateException("Invalid jmx name " + jmxName, e); throw new IllegalStateException("Invalid jmx name " + jmxName, ex);
} }
} }
private void load(String... environment) { private void load(String... environment) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
EnvironmentTestUtils.addEnvironment(applicationContext, environment); EnvironmentTestUtils.addEnvironment(applicationContext, environment);
applicationContext.register(JmxAutoConfiguration.class, SpringApplicationLifecycleAutoConfiguration.class); applicationContext.register(JmxAutoConfiguration.class,
SpringApplicationLifecycleAutoConfiguration.class);
applicationContext.refresh(); applicationContext.refresh();
this.context = applicationContext; this.context = applicationContext;
} }
} }
...@@ -55,13 +55,6 @@ public class RunProcess { ...@@ -55,13 +55,6 @@ public class RunProcess {
return run(waitForProcess, Arrays.asList(args)); return run(waitForProcess, Arrays.asList(args));
} }
/**
* Kill this process.
*/
public void kill() {
doKill();
}
protected int run(boolean waitForProcess, Collection<String> args) throws IOException { protected int run(boolean waitForProcess, Collection<String> args) throws IOException {
ProcessBuilder builder = new ProcessBuilder(this.command); ProcessBuilder builder = new ProcessBuilder(this.command);
builder.command().addAll(args); builder.command().addAll(args);
...@@ -131,7 +124,7 @@ public class RunProcess { ...@@ -131,7 +124,7 @@ public class RunProcess {
return true; return true;
} }
} }
catch (Exception e) { catch (Exception ex) {
return true; return true;
} }
return false; return false;
...@@ -180,6 +173,13 @@ public class RunProcess { ...@@ -180,6 +173,13 @@ public class RunProcess {
} }
/**
* Kill this process.
*/
public void kill() {
doKill();
}
private boolean doKill() { private boolean doKill() {
// destroy the running process // destroy the running process
Process process = this.process; Process process = this.process;
...@@ -194,7 +194,6 @@ public class RunProcess { ...@@ -194,7 +194,6 @@ public class RunProcess {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} }
} }
return false; return false;
} }
......
...@@ -22,8 +22,7 @@ import javax.management.MBeanServer; ...@@ -22,8 +22,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
/** /**
* This sample app simulates the JMX Mbean that is exposed by the Spring * This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
* Boot application.
*/ */
public class SampleApplication { public class SampleApplication {
...@@ -31,7 +30,8 @@ public class SampleApplication { ...@@ -31,7 +30,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 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(); SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name); mbs.registerMBean(mbean, name);
...@@ -41,7 +41,8 @@ public class SampleApplication { ...@@ -41,7 +41,8 @@ public class SampleApplication {
int waitAttempts = 0; int waitAttempts = 0;
while (!mbean.shutdownInvoked) { while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) { 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) { synchronized (lock) {
lock.wait(250); lock.wait(250);
...@@ -50,7 +51,6 @@ public class SampleApplication { ...@@ -50,7 +51,6 @@ public class SampleApplication {
} }
} }
public interface SpringApplicationLifecycleMXBean { public interface SpringApplicationLifecycleMXBean {
boolean isReady(); boolean isReady();
...@@ -76,5 +76,7 @@ public class SampleApplication { ...@@ -76,5 +76,7 @@ public class SampleApplication {
this.shutdownInvoked = true; this.shutdownInvoked = true;
System.out.println("Shutdown requested"); System.out.println("Shutdown requested");
} }
} }
}
\ No newline at end of file }
...@@ -21,8 +21,7 @@ import javax.management.MBeanServer; ...@@ -21,8 +21,7 @@ import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
/** /**
* This sample app simulates the JMX Mbean that is exposed by the Spring * This sample app simulates the JMX Mbean that is exposed by the Spring Boot application.
* Boot application.
*/ */
public class SampleApplication { public class SampleApplication {
...@@ -30,7 +29,8 @@ public class SampleApplication { ...@@ -30,7 +29,8 @@ public class SampleApplication {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 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(); SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
mbs.registerMBean(mbean, name); mbs.registerMBean(mbean, name);
...@@ -40,7 +40,8 @@ public class SampleApplication { ...@@ -40,7 +40,8 @@ public class SampleApplication {
int waitAttempts = 0; int waitAttempts = 0;
while (!mbean.shutdownInvoked) { while (!mbean.shutdownInvoked) {
if (waitAttempts > 10) { 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) { synchronized (lock) {
lock.wait(250); lock.wait(250);
...@@ -49,7 +50,6 @@ public class SampleApplication { ...@@ -49,7 +50,6 @@ public class SampleApplication {
} }
} }
public interface SpringApplicationLifecycleMXBean { public interface SpringApplicationLifecycleMXBean {
boolean isReady(); boolean isReady();
...@@ -75,5 +75,7 @@ public class SampleApplication { ...@@ -75,5 +75,7 @@ public class SampleApplication {
this.shutdownInvoked = true; this.shutdownInvoked = true;
System.out.println("Shutdown requested"); System.out.println("Shutdown requested");
} }
} }
}
\ No newline at end of file }
...@@ -36,7 +36,6 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -36,7 +36,6 @@ import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter; import org.apache.maven.shared.artifact.filter.collection.AbstractArtifactFeatureFilter;
import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts; import org.apache.maven.shared.artifact.filter.collection.FilterArtifacts;
import org.springframework.boot.loader.tools.FileUtils; import org.springframework.boot.loader.tools.FileUtils;
import org.springframework.boot.loader.tools.MainClassFinder; import org.springframework.boot.loader.tools.MainClassFinder;
...@@ -137,8 +136,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -137,8 +136,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @return {@code true} if the application process should be forked * @return {@code true} if the application process should be forked
*/ */
protected boolean isFork() { protected boolean isFork() {
return (Boolean.TRUE.equals(this.fork) return (Boolean.TRUE.equals(this.fork) || (this.fork == null && (hasAgent() || hasJvmArgs())));
|| (this.fork == null && (hasAgent() || hasJvmArgs())));
} }
private boolean hasAgent() { private boolean hasAgent() {
...@@ -165,7 +163,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -165,7 +163,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
} }
CodeSource source = loaded.getProtectionDomain().getCodeSource(); CodeSource source = loaded.getProtectionDomain().getCodeSource();
if (source != null) { 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 { ...@@ -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(); findAgent();
if (isFork()) { if (isFork()) {
doRunWithForkedJvm(startClassName); doRunWithForkedJvm(startClassName);
...@@ -196,8 +195,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -196,8 +195,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
} }
} }
private void doRunWithForkedJvm(String startClassName) private void doRunWithForkedJvm(String startClassName) throws MojoExecutionException,
throws MojoExecutionException, MojoFailureException { MojoFailureException {
List<String> args = new ArrayList<String>(); List<String> args = new ArrayList<String>();
addAgents(args); addAgents(args);
addJvmArgs(args); addJvmArgs(args);
...@@ -213,8 +212,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -213,8 +212,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
* @throws MojoExecutionException * @throws MojoExecutionException
* @throws MojoFailureException * @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. * Run with the current VM, using the specified arguments.
...@@ -277,8 +276,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -277,8 +276,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
args.add("-cp"); args.add("-cp");
args.add(classpath.toString()); args.add(classpath.toString());
} }
catch (Exception e) { catch (Exception ex) {
throw new MojoExecutionException("Could not build classpath", e); throw new MojoExecutionException("Could not build classpath", ex);
} }
} }
...@@ -358,8 +357,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -358,8 +357,8 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
getLog().debug(sb.toString().trim()); getLog().debug(sb.toString().trim());
} }
private static class TestArtifactFilter extends AbstractArtifactFeatureFilter { private static class TestArtifactFilter extends AbstractArtifactFeatureFilter {
public TestArtifactFilter() { public TestArtifactFilter() {
super("", Artifact.SCOPE_TEST); super("", Artifact.SCOPE_TEST);
} }
...@@ -368,6 +367,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -368,6 +367,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
protected String getArtifactFeature(Artifact artifact) { protected String getArtifactFeature(Artifact artifact) {
return artifact.getScope(); return artifact.getScope();
} }
} }
/** /**
...@@ -423,7 +423,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo { ...@@ -423,7 +423,7 @@ public abstract class AbstractRunMojo extends AbstractDependencyFilterMojo {
if (!mainMethod.isAccessible()) { if (!mainMethod.isAccessible()) {
mainMethod.setAccessible(true); mainMethod.setAccessible(true);
} }
mainMethod.invoke(null, new Object[] {this.args}); mainMethod.invoke(null, new Object[] { this.args });
} }
catch (NoSuchMethodException ex) { catch (NoSuchMethodException ex) {
Exception wrappedEx = new Exception( Exception wrappedEx = new Exception(
......
...@@ -42,7 +42,7 @@ class RunArguments { ...@@ -42,7 +42,7 @@ class RunArguments {
} }
public LinkedList<String> getArgs() { public LinkedList<String> getArgs() {
return args; return this.args;
} }
public String[] asArray() { public String[] asArray() {
......
...@@ -24,7 +24,6 @@ import org.apache.maven.plugins.annotations.Execute; ...@@ -24,7 +24,6 @@ import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.plugins.annotations.ResolutionScope;
import org.springframework.boot.loader.tools.JavaExecutable; import org.springframework.boot.loader.tools.JavaExecutable;
import org.springframework.boot.loader.tools.RunProcess; import org.springframework.boot.loader.tools.RunProcess;
...@@ -41,15 +40,17 @@ public class RunMojo extends AbstractRunMojo { ...@@ -41,15 +40,17 @@ public class RunMojo extends AbstractRunMojo {
@Override @Override
protected void runWithForkedJvm(List<String> args) throws MojoExecutionException { protected void runWithForkedJvm(List<String> args) throws MojoExecutionException {
try { try {
new RunProcess(new JavaExecutable().toString()).run(true, args new RunProcess(new JavaExecutable().toString()).run(true,
.toArray(new String[args.size()])); args.toArray(new String[args.size()]));
} }
catch (Exception ex) { catch (Exception ex) {
throw new MojoExecutionException("Could not exec java", 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); IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(startClassName);
Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName, Thread launchThread = new Thread(threadGroup, new LaunchRunner(startClassName,
arguments), startClassName + ".main()"); arguments), startClassName + ".main()");
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.maven; package org.springframework.boot.maven;
import java.io.IOException; import java.io.IOException;
import javax.management.AttributeNotFoundException; import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanException; import javax.management.MBeanException;
...@@ -35,20 +36,19 @@ import org.apache.maven.plugin.MojoExecutionException; ...@@ -35,20 +36,19 @@ import org.apache.maven.plugin.MojoExecutionException;
* information about the lifecycle of a given Spring application. * information about the lifecycle of a given Spring application.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @since 1.3.0
*/ */
class SpringApplicationLifecycleClient { class SpringApplicationLifecycleClient {
//Note: see org.springframework.boot.autoconfigure.test.SpringApplicationLifecycleAutoConfiguration // Note: see SpringApplicationLifecycleAutoConfiguration
static final String DEFAULT_OBJECT_NAME = static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
private final MBeanServerConnection mBeanServerConnection; private final MBeanServerConnection connection;
private final ObjectName objectName; private final ObjectName objectName;
public SpringApplicationLifecycleClient(MBeanServerConnection mBeanServerConnection, String jmxName) { public SpringApplicationLifecycleClient(MBeanServerConnection connection,
this.mBeanServerConnection = mBeanServerConnection; String jmxName) {
this.connection = connection;
this.objectName = toObjectName(jmxName); this.objectName = toObjectName(jmxName);
} }
...@@ -66,30 +66,32 @@ class SpringApplicationLifecycleClient { ...@@ -66,30 +66,32 @@ class SpringApplicationLifecycleClient {
} }
/** /**
* Check if the spring application managed by this instance is ready. * Check if the spring application managed by this instance is ready. Returns
* <p>Returns {@code false} if the mbean is not yet deployed so this method * {@code false} if the mbean is not yet deployed so this method should be repeatedly
* should be repeatedly called until a timeout is reached. * called until a timeout is reached.
* @return {@code true} if the application is ready to service requests * @return {@code true} if the application is ready to service requests
* @throws MojoExecutionException if the JMX service could not be contacted * @throws MojoExecutionException if the JMX service could not be contacted
*/ */
public boolean isReady() throws MojoExecutionException { public boolean isReady() throws MojoExecutionException {
try { 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 return false; // Instance not available yet
} }
catch (AttributeNotFoundException e) { catch (AttributeNotFoundException ex) {
throw new IllegalStateException("Unexpected: attribute 'Ready' not available", e); throw new IllegalStateException(
"Unexpected: attribute 'Ready' not available", ex);
} }
catch (ReflectionException e) { catch (ReflectionException ex) {
throw new MojoExecutionException("Failed to retrieve Ready attribute", e.getCause()); throw new MojoExecutionException("Failed to retrieve Ready attribute",
ex.getCause());
} }
catch (MBeanException e) { catch (MBeanException ex) {
throw new MojoExecutionException(e.getMessage(), e); throw new MojoExecutionException(ex.getMessage(), ex);
} }
catch (IOException e) { catch (IOException ex) {
throw new MojoExecutionException(e.getMessage(), e); throw new MojoExecutionException(ex.getMessage(), ex);
} }
} }
...@@ -99,15 +101,16 @@ class SpringApplicationLifecycleClient { ...@@ -99,15 +101,16 @@ class SpringApplicationLifecycleClient {
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
* @throws InstanceNotFoundException if the lifecycle mbean cannot be found * @throws InstanceNotFoundException if the lifecycle mbean cannot be found
*/ */
public void stop() throws MojoExecutionException, IOException, InstanceNotFoundException { public void stop() throws MojoExecutionException, IOException,
InstanceNotFoundException {
try { try {
this.mBeanServerConnection.invoke(this.objectName, "shutdown", null, null); this.connection.invoke(this.objectName, "shutdown", null, null);
} }
catch (ReflectionException e) { catch (ReflectionException ex) {
throw new MojoExecutionException("Shutdown failed", e.getCause()); throw new MojoExecutionException("Shutdown failed", ex.getCause());
} }
catch (MBeanException e) { catch (MBeanException ex) {
throw new MojoExecutionException("Could not invoke shutdown operation", e); throw new MojoExecutionException("Could not invoke shutdown operation", ex);
} }
} }
......
...@@ -18,6 +18,7 @@ package org.springframework.boot.maven; ...@@ -18,6 +18,7 @@ package org.springframework.boot.maven;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection; import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnector;
...@@ -40,23 +41,23 @@ import org.apache.maven.plugins.annotations.Parameter; ...@@ -40,23 +41,23 @@ import org.apache.maven.plugins.annotations.Parameter;
public class StopMojo extends AbstractMojo { public class StopMojo extends AbstractMojo {
/** /**
* Flag to indicate if the run processes should be forked. Must be aligned to the value * Flag to indicate if the run processes should be forked. Must be aligned to the
* used to {@link StartMojo start} the process * value used to {@link StartMojo start} the process
* @since 1.2 * @since 1.2
*/ */
@Parameter(property = "fork") @Parameter(property = "fork")
private Boolean fork; private Boolean fork;
/** /**
* The JMX name of the automatically deployed MBean managing the lifecycle * The JMX name of the automatically deployed MBean managing the lifecycle of the
* of the application. * application.
*/ */
@Parameter @Parameter
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME; private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
/** /**
* The port to use to lookup the platform MBeanServer if the application * The port to use to lookup the platform MBeanServer if the application has been
* has been forked. * forked.
*/ */
@Parameter @Parameter
private int jmxPort = 9001; private int jmxPort = 9001;
...@@ -72,36 +73,38 @@ public class StopMojo extends AbstractMojo { ...@@ -72,36 +73,38 @@ public class StopMojo extends AbstractMojo {
stop(); stop();
} }
} }
catch (IOException e) { catch (IOException ex) {
// The response won't be received as the server has died - ignoring // 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 stop() throws IOException, MojoFailureException, MojoExecutionException { private void stopForkedProcess() throws IOException, MojoFailureException,
doStop(ManagementFactory.getPlatformMBeanServer()); MojoExecutionException {
} JMXConnector connector = SpringApplicationLifecycleClient
.createLocalJmxConnector(this.jmxPort);
private void stopForkedProcess() throws IOException, MojoFailureException, MojoExecutionException {
JMXConnector jmxConnector = SpringApplicationLifecycleClient.createLocalJmxConnector(this.jmxPort);
try { try {
MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection(); MBeanServerConnection connection = connector.getMBeanServerConnection();
doStop(mBeanServerConnection); doStop(connection);
} }
finally { finally {
jmxConnector.close(); connector.close();
} }
} }
private void doStop(MBeanServerConnection connection) private void stop() throws IOException, MojoFailureException, MojoExecutionException {
throws IOException, MojoExecutionException { doStop(ManagementFactory.getPlatformMBeanServer());
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(connection, this.jmxName); }
private void doStop(MBeanServerConnection connection) throws IOException,
MojoExecutionException {
try { try {
helper.stop(); new SpringApplicationLifecycleClient(connection, this.jmxName).stop();
} }
catch (InstanceNotFoundException e) { catch (InstanceNotFoundException ex) {
throw new MojoExecutionException("Spring application lifecycle JMX bean not found (fork is " + throw new MojoExecutionException(
"" + this.fork + "). Could not stop application gracefully", e); "Spring application lifecycle JMX bean not found (fork is " + ""
+ this.fork + "). Could not stop application gracefully", ex);
} }
} }
......
...@@ -17,13 +17,13 @@ ...@@ -17,13 +17,13 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
import javax.management.ObjectName; import javax.management.ObjectName;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
...@@ -52,50 +52,51 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw ...@@ -52,50 +52,51 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw
private boolean ready = false; private boolean ready = false;
public SpringApplicationLifecycleRegistrar(String name) throws MalformedObjectNameException { public SpringApplicationLifecycleRegistrar(String name)
throws MalformedObjectNameException {
this.objectName = new ObjectName(name); this.objectName = new ObjectName(name);
} }
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { public void setApplicationContext(ApplicationContext applicationContext)
Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext, throws BeansException {
Assert.state(applicationContext instanceof ConfigurableApplicationContext,
"ApplicationContext does not implement ConfigurableApplicationContext"); "ApplicationContext does not implement ConfigurableApplicationContext");
this.applicationContext = (ConfigurableApplicationContext) applicationContext; this.applicationContext = (ConfigurableApplicationContext) applicationContext;
} }
@Override @Override
public void onApplicationEvent(ApplicationReadyEvent event) { public void onApplicationEvent(ApplicationReadyEvent event) {
ready = true; this.ready = true;
} }
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.registerMBean(new SpringApplicationLifecycle(), objectName); server.registerMBean(new SpringApplicationLifecycle(), this.objectName);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Application lifecycle MBean registered with name '" + objectName + "'"); logger.debug("Application lifecycle MBean registered with name '"
+ this.objectName + "'");
} }
} }
@Override @Override
public void destroy() throws Exception { public void destroy() throws Exception {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(objectName); ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.objectName);
} }
private class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean { private class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean {
@Override @Override
public boolean isReady() { public boolean isReady() {
return ready; return SpringApplicationLifecycleRegistrar.this.ready;
} }
@Override @Override
public void shutdown() { public void shutdown() {
logger.info("Application shutdown requested."); logger.info("Application shutdown requested.");
applicationContext.close(); SpringApplicationLifecycleRegistrar.this.applicationContext.close();
} }
} }
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.context; package org.springframework.boot.context;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import javax.management.InstanceNotFoundException; import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException; import javax.management.MalformedObjectNameException;
...@@ -27,7 +28,6 @@ import org.junit.Before; ...@@ -27,7 +28,6 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
...@@ -75,15 +75,18 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -75,15 +75,18 @@ public class SpringApplicationLifecycleRegistrarTests {
@Override @Override
public void onApplicationEvent(ContextRefreshedEvent event) { public void onApplicationEvent(ContextRefreshedEvent event) {
try { try {
assertFalse("Application should not be ready yet", isCurrentApplicationReady(objectName)); assertFalse("Application should not be ready yet",
isCurrentApplicationReady(objectName));
} }
catch (Exception e) { catch (Exception ex) {
throw new IllegalStateException("Could not contact spring application lifecycle bean", e); throw new IllegalStateException(
"Could not contact spring application lifecycle bean", ex);
} }
} }
}); });
this.context = application.run(); this.context = application.run();
assertTrue("application should be ready now", isCurrentApplicationReady(objectName)); assertTrue("application should be ready now",
isCurrentApplicationReady(objectName));
} }
@Test @Test
...@@ -95,8 +98,7 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -95,8 +98,7 @@ public class SpringApplicationLifecycleRegistrarTests {
assertTrue("application should be running", this.context.isRunning()); assertTrue("application should be running", this.context.isRunning());
invokeShutdown(objectName); invokeShutdown(objectName);
assertFalse("application should not be running", this.context.isRunning()); assertFalse("application should not be running", this.context.isRunning());
this.thrown.expect(InstanceNotFoundException.class); // JMX cleanup
thrown.expect(InstanceNotFoundException.class); // JMX cleanup
this.mBeanServer.getObjectInstance(objectName); this.mBeanServer.getObjectInstance(objectName);
} }
...@@ -127,16 +129,15 @@ public class SpringApplicationLifecycleRegistrarTests { ...@@ -127,16 +129,15 @@ public class SpringApplicationLifecycleRegistrarTests {
} }
} }
@Configuration @Configuration
static class Config { static class Config {
@Bean @Bean
public SpringApplicationLifecycleRegistrar springApplicationLifecycle() public SpringApplicationLifecycleRegistrar springApplicationLifecycle()
throws MalformedObjectNameException { throws MalformedObjectNameException {
return new SpringApplicationLifecycleRegistrar(OBJECT_NAME); return new SpringApplicationLifecycleRegistrar(OBJECT_NAME);
} }
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment