diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java new file mode 100644 index 0000000000..c782dfe335 --- /dev/null +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/ShutdownParentEndpointTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.actuate.endpoint; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link ShutdownEndpoint}. + * + * @author Dave Syer + */ +public class ShutdownParentEndpointTests { + + private ConfigurableApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void shutdownChild() throws Exception { + this.context = new SpringApplicationBuilder(Config.class).child(Empty.class) + .web(false).run(); + assertThat((String) getEndpointBean().invoke().get("message"), + startsWith("Shutting down")); + assertTrue(this.context.isActive()); + Thread.sleep(600); + assertFalse(this.context.isActive()); + } + + @Test + public void shutdownParent() throws Exception { + this.context = new SpringApplicationBuilder(Empty.class).child(Config.class) + .web(false).run(); + assertThat((String) getEndpointBean().invoke().get("message"), + startsWith("Shutting down")); + assertTrue(this.context.isActive()); + Thread.sleep(600); + assertFalse(this.context.isActive()); + } + + private ShutdownEndpoint getEndpointBean() { + return this.context.getBean(ShutdownEndpoint.class); + } + + @Configuration + @EnableConfigurationProperties + public static class Config { + + @Bean + public ShutdownEndpoint endpoint() { + ShutdownEndpoint endpoint = new ShutdownEndpoint(); + endpoint.setEnabled(true); + return endpoint; + } + + } + + @Configuration + public static class Empty { + } +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java index 719de7ce82..4cd5d70381 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java @@ -18,16 +18,22 @@ package org.springframework.boot.context.initializer; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.Ordered; /** - * {@link ApplicationContextInitializer} for setting the parent context. + * {@link ApplicationContextInitializer} for setting the parent context. Also publishes + * {@link ParentContextAvailableEvent} when the context is refreshed to signal to other + * listeners that the context is available and has a parent. * * @author Dave Syer */ public class ParentContextApplicationContextInitializer implements - ApplicationContextInitializer, Ordered { + ApplicationContextInitializer, + ApplicationListener, Ordered { private int order = Integer.MIN_VALUE; @@ -46,9 +52,31 @@ public class ParentContextApplicationContextInitializer implements return this.order; } + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + ApplicationContext context = event.getApplicationContext(); + if (context instanceof ConfigurableApplicationContext) { + context.publishEvent(new ParentContextAvailableEvent( + (ConfigurableApplicationContext) context)); + } + } + @Override public void initialize(ConfigurableApplicationContext applicationContext) { applicationContext.setParent(this.parent); } + public static class ParentContextAvailableEvent extends ApplicationEvent { + + public ParentContextAvailableEvent( + ConfigurableApplicationContext applicationContext) { + super(applicationContext); + } + + public ConfigurableApplicationContext getApplicationContext() { + return (ConfigurableApplicationContext) getSource(); + } + + } + } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java b/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java new file mode 100644 index 0000000000..afe631646d --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/listener/ParentContextCloserListener.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.context.listener; + +import org.springframework.boot.context.initializer.ParentContextApplicationContextInitializer.ParentContextAvailableEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.core.Ordered; + +/** + * Listener that closes the application context if its parent is closed. It listens for + * refresh events and grabs the current context from there, and then listens for closed + * events and propagates it down the hierarchy. + * + * @author Dave Syer + */ +public class ParentContextCloserListener implements + ApplicationListener, Ordered { + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE - 10; + } + + @Override + public void onApplicationEvent(ParentContextAvailableEvent event) { + maybeInstallListenerInParent(event.getApplicationContext()); + } + + private void maybeInstallListenerInParent(ConfigurableApplicationContext child) { + if (child.getParent() instanceof ConfigurableApplicationContext) { + ConfigurableApplicationContext parent = (ConfigurableApplicationContext) child + .getParent(); + parent.addApplicationListener(new ContextCloserListener(child)); + } + } + + protected static class ContextCloserListener implements + ApplicationListener { + + private ConfigurableApplicationContext context; + + public ContextCloserListener(ConfigurableApplicationContext context) { + this.context = context; + + } + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + if (this.context != null + && event.getApplicationContext() == this.context.getParent() + && this.context.isActive()) { + this.context.close(); + } + } + + } + +} \ No newline at end of file diff --git a/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot/src/main/resources/META-INF/spring.factories index f1f56e8994..02303c4140 100644 --- a/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot/src/main/resources/META-INF/spring.factories @@ -11,5 +11,6 @@ org.springframework.boot.context.listener.EnvironmentDelegateApplicationListener org.springframework.boot.context.listener.FileEncodingApplicationListener,\ org.springframework.boot.context.listener.LoggingApplicationListener,\ org.springframework.boot.context.listener.VcapApplicationListener,\ +org.springframework.boot.context.listener.ParentContextCloserListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorInitializer