diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java index e743bdb1ec..672976db73 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java @@ -17,20 +17,27 @@ package org.springframework.boot.actuate.system; import java.io.File; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.boot.ApplicationPid; -import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.bind.RelaxedPropertyResolver; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.event.ApplicationPreparedEvent; +import org.springframework.boot.context.event.SpringApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.Ordered; +import org.springframework.core.env.Environment; import org.springframework.util.Assert; /** * An {@link ApplicationListener} that saves application PID into file. This application * listener will be triggered exactly once per JVM, and the file name can be overridden at - * runtime with a System property or environment variable named "PIDFILE" (or "pidfile"). + * runtime with a System property or environment variable named "PIDFILE" (or "pidfile") + * or using a {@code spring.application.pidfile} property in the Spring + * {@link Environment}. * * @author Jakub Kubrynski * @author Dave Syer @@ -38,13 +45,15 @@ import org.springframework.util.Assert; * @since 1.2.0 */ public class ApplicationPidFileWriter implements - ApplicationListener, Ordered { + ApplicationListener, Ordered { private static final Log logger = LogFactory.getLog(ApplicationPidFileWriter.class); private static final String DEFAULT_FILE_NAME = "application.pid"; - private static final String[] PROPERTY_VARIABLES = { "PIDFILE", "pidfile" }; + private static final String[] SYSTEM_PROPERTY_VARIABLES = { "PIDFILE", "pidfile" }; + + private static final String SPRING_PROPERTY = "spring.application.pidfile"; private static final AtomicBoolean created = new AtomicBoolean(false); @@ -52,12 +61,14 @@ public class ApplicationPidFileWriter implements private final File file; + private Class triggerEventType = ApplicationPreparedEvent.class; + /** * Create a new {@link ApplicationPidFileWriter} instance using the filename * 'application.pid'. */ public ApplicationPidFileWriter() { - this.file = new File(DEFAULT_FILE_NAME); + this(new File(DEFAULT_FILE_NAME)); } /** @@ -74,28 +85,66 @@ public class ApplicationPidFileWriter implements */ public ApplicationPidFileWriter(File file) { Assert.notNull(file, "File must not be null"); - String override = SystemProperties.get(PROPERTY_VARIABLES); - if (override != null) { - this.file = new File(override); - } - else { - this.file = file; - } + this.file = file; + } + + /** + * Sets the type of application event that will trigger writing of the PID file. + * Defaults to {@link ApplicationPreparedEvent}. NOTE: If you use the + * {@link ApplicationPreparedEvent} to trigger the write, you will not be able to + * specify the PID filename in the Spring {@link Environment}. + */ + public void setTriggerEventType( + Class triggerEventType) { + Assert.notNull(triggerEventType, "Trigger event type must not be null"); + this.triggerEventType = triggerEventType; } @Override - public void onApplicationEvent(ApplicationStartedEvent event) { - if (created.compareAndSet(false, true)) { - try { - new ApplicationPid().write(this.file); - this.file.deleteOnExit(); - } - catch (Exception ex) { - logger.warn(String.format("Cannot create pid file %s", this.file)); + public void onApplicationEvent(SpringApplicationEvent event) { + if (this.triggerEventType.isInstance(event)) { + if (created.compareAndSet(false, true)) { + try { + writePidFile(event); + } + catch (Exception ex) { + logger.warn(String.format("Cannot create pid file %s", this.file)); + } } } } + private void writePidFile(SpringApplicationEvent event) throws IOException { + File pidFile = this.file; + String override = SystemProperties.get(SYSTEM_PROPERTY_VARIABLES); + if (override != null) { + pidFile = new File(override); + } + else { + Environment environment = getEnvironment(event); + if (environment != null) { + override = new RelaxedPropertyResolver(environment) + .getProperty(SPRING_PROPERTY); + if (override != null) { + pidFile = new File(override); + } + } + } + new ApplicationPid().write(pidFile); + pidFile.deleteOnExit(); + } + + private Environment getEnvironment(SpringApplicationEvent event) { + if (event instanceof ApplicationEnvironmentPreparedEvent) { + return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment(); + } + if (event instanceof ApplicationPreparedEvent) { + return ((ApplicationPreparedEvent) event).getApplicationContext() + .getEnvironment(); + } + return null; + } + public void setOrder(int order) { this.order = order; } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidListener.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidListener.java index 9c990e970e..4c3b2d66d9 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidListener.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidListener.java @@ -16,6 +16,10 @@ package org.springframework.boot.actuate.system; +import java.io.File; + +import org.springframework.boot.context.event.ApplicationStartedEvent; + /** * An {@link org.springframework.context.ApplicationListener} that saves application PID * into file. This application listener will be triggered exactly once per JVM, and the @@ -31,4 +35,19 @@ package org.springframework.boot.actuate.system; @Deprecated public class ApplicationPidListener extends ApplicationPidFileWriter { + public ApplicationPidListener() { + super(); + setTriggerEventType(ApplicationStartedEvent.class); + } + + public ApplicationPidListener(File file) { + super(file); + setTriggerEventType(ApplicationStartedEvent.class); + } + + public ApplicationPidListener(String filename) { + super(filename); + setTriggerEventType(ApplicationStartedEvent.class); + } + } diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/ApplicationPidFileWriterTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/ApplicationPidFileWriterTests.java index 9ed37ca9a9..4672a9ad31 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/ApplicationPidFileWriterTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/system/ApplicationPidFileWriterTests.java @@ -25,23 +25,33 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.boot.context.event.ApplicationPreparedEvent; import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.mock.env.MockPropertySource; import org.springframework.util.FileCopyUtils; import static org.hamcrest.Matchers.isEmptyString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link ApplicationPidFileWriter}. * * @author Jakub Kubrynski * @author Dave Syer + * @author Phillip Webb */ public class ApplicationPidFileWriterTests { - private static final ApplicationStartedEvent EVENT = new ApplicationStartedEvent( - new SpringApplication(), new String[] {}); + private static final ApplicationPreparedEvent EVENT = new ApplicationPreparedEvent( + new SpringApplication(), new String[] {}, + mock(ConfigurableApplicationContext.class)); @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -72,4 +82,47 @@ public class ApplicationPidFileWriterTests { not(isEmptyString())); } + @Test + public void overridePidFileWithSpring() throws Exception { + File file = this.temporaryFolder.newFile(); + ConfigurableEnvironment environment = new StandardEnvironment(); + MockPropertySource propertySource = new MockPropertySource(); + propertySource.setProperty("spring.application.pidfile", file.getAbsolutePath()); + environment.getPropertySources().addLast(propertySource); + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + given(context.getEnvironment()).willReturn(environment); + ApplicationPreparedEvent event = new ApplicationPreparedEvent( + new SpringApplication(), new String[] {}, context); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(); + listener.onApplicationEvent(event); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString())); + } + + @Test + public void differentEventTypes() throws Exception { + File file = this.temporaryFolder.newFile(); + ConfigurableEnvironment environment = new StandardEnvironment(); + MockPropertySource propertySource = new MockPropertySource(); + propertySource.setProperty("spring.application.pidfile", file.getAbsolutePath()); + environment.getPropertySources().addLast(propertySource); + ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent( + new SpringApplication(), new String[] {}, environment); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(); + listener.onApplicationEvent(event); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString()); + listener.setTriggerEventType(ApplicationEnvironmentPreparedEvent.class); + listener.onApplicationEvent(event); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString())); + } + + @Test + public void withNoEnvironment() throws Exception { + File file = this.temporaryFolder.newFile(); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + listener.setTriggerEventType(ApplicationStartedEvent.class); + listener.onApplicationEvent(new ApplicationStartedEvent(new SpringApplication(), + new String[] {})); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString())); + } + }