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 6ae378fe6e..0416d78669 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 @@ -18,6 +18,9 @@ package org.springframework.boot.actuate.system; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.logging.Log; @@ -36,11 +39,21 @@ 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") - * or using a {@code spring.pidfile} property in the Spring {@link Environment}. + * or using a {@code spring.pid.file} property in the Spring {@link Environment}. + *

+ * If PID file can not be created no exception is reported. This behavior can be changed + * by assigning {@code true} to System property or environment variable named + * {@code PID_FAIL_ON_WRITE_ERROR} (or "pid_fail_on_write_error") or to + * {@code spring.pid.fail-on-write-error} property in the Spring {@link Environment}. + *

+ * Note: access to the Spring {@link Environment} is only possible when the + * {@link #setTriggerEventType(Class) triggerEventType} is set to + * {@link ApplicationEnvironmentPreparedEvent} or {@link ApplicationPreparedEvent}. * * @author Jakub Kubrynski * @author Dave Syer * @author Phillip Webb + * @author Tomasz Przybyla * @since 1.2.0 */ public class ApplicationPidFileWriter implements @@ -50,9 +63,22 @@ public class ApplicationPidFileWriter implements private static final String DEFAULT_FILE_NAME = "application.pid"; - private static final String[] SYSTEM_PROPERTY_VARIABLES = { "PIDFILE", "pidfile" }; + private static final List FILE_PROPERTIES; + static { + List properties = new ArrayList(); + properties.add(new SpringProperty("spring.pid.", "file")); + properties.add(new SpringProperty("spring.", "pidfile")); + properties.add(new SystemProperty("PIDFILE")); + FILE_PROPERTIES = Collections.unmodifiableList(properties); + } - private static final String SPRING_PROPERTY = "spring.pidfile"; + private static final List FAIL_ON_WRITE_ERROR_PROPERTIES; + static { + List properties = new ArrayList(); + properties.add(new SpringProperty("spring.pid.", "fail-on-write-error")); + properties.add(new SystemProperty("PID_FAIL_ON_WRITE_ERROR")); + FAIL_ON_WRITE_ERROR_PROPERTIES = Collections.unmodifiableList(properties); + } private static final AtomicBoolean created = new AtomicBoolean(false); @@ -108,7 +134,12 @@ public class ApplicationPidFileWriter implements writePidFile(event); } catch (Exception ex) { - logger.warn(String.format("Cannot create pid file %s", this.file), ex); + String message = String + .format("Cannot create pid file %s", this.file); + if (failOnWriteError(event)) { + throw new IllegalStateException(message, ex); + } + logger.warn(message, ex); } } } @@ -116,31 +147,25 @@ public class ApplicationPidFileWriter implements private void writePidFile(SpringApplicationEvent event) throws IOException { File pidFile = this.file; - String override = SystemProperties.get(SYSTEM_PROPERTY_VARIABLES); + String override = getProperty(event, FILE_PROPERTIES); 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(); + private boolean failOnWriteError(SpringApplicationEvent event) { + String value = getProperty(event, FAIL_ON_WRITE_ERROR_PROPERTIES); + return (value == null ? false : Boolean.parseBoolean(value)); + } + + private String getProperty(SpringApplicationEvent event, List candidates) { + for (Property candidate : candidates) { + String value = candidate.getValue(event); + if (value != null) { + return value; + } } return null; } @@ -160,4 +185,69 @@ public class ApplicationPidFileWriter implements static void reset() { created.set(false); } + + /** + * Provides access to a property value. + */ + private static interface Property { + + String getValue(SpringApplicationEvent event); + + } + + /** + * {@link Property} obtained from Spring's {@link Environment}. + */ + private static class SpringProperty implements Property { + + private final String prexfix; + + private final String key; + + public SpringProperty(String prefix, String key) { + this.prexfix = prefix; + this.key = key; + } + + @Override + public String getValue(SpringApplicationEvent event) { + Environment environment = getEnvironment(event); + if (environment == null) { + return null; + } + return new RelaxedPropertyResolver(environment, this.prexfix) + .getProperty(this.key); + } + + private Environment getEnvironment(SpringApplicationEvent event) { + if (event instanceof ApplicationEnvironmentPreparedEvent) { + return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment(); + } + if (event instanceof ApplicationPreparedEvent) { + return ((ApplicationPreparedEvent) event).getApplicationContext() + .getEnvironment(); + } + return null; + } + + } + + /** + * {@link Property} obtained from {@link SystemProperties}. + */ + private static class SystemProperty implements Property { + + private final String[] properties; + + public SystemProperty(String name) { + this.properties = new String[] { name.toUpperCase(), name.toLowerCase() }; + } + + @Override + public String getValue(SpringApplicationEvent event) { + return SystemProperties.get(this.properties); + } + + } + } diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java index 63d1fb24ef..fd4d609ff8 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/SystemProperties.java @@ -17,6 +17,8 @@ package org.springframework.boot.actuate.system; /** + * Access to system properties. + * * @author Phillip Webb */ class SystemProperties { diff --git a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json index e00ac85f86..29f1aea85b 100644 --- a/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-actuator/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -87,9 +87,22 @@ "type": "java.lang.String", "description": "Resource reference to a generated git info properties file." }, + { + "name": "spring.pid.file", + "type": "java.lang.String", + "description": "Location of the PID file to write (if ApplicationPidFileWriter is used).", + "sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter" + } + { + "name": "spring.pid.fail-on-write-error", + "type": "java.lang.Boolean", + "description": "Fail if ApplicationPidFileWriter is used but it cannot write the PID file.", + "sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter" + } { "name": "spring.pidfile", "type": "java.lang.String", + "deprecated:" true, "description": "Location of the PID file to write (if ApplicationPidFileWriter is used).", "sourceType": "org.springframework.boot.actuate.system.ApplicationPidFileWriter" } 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 d46ea346eb..0ad3e07738 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 @@ -1,5 +1,5 @@ /* - * Copyright 2010-2014 the original author or authors. + * Copyright 2010-2015 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. @@ -23,11 +23,13 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; 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.boot.context.event.SpringApplicationEvent; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; @@ -46,6 +48,7 @@ import static org.mockito.Mockito.mock; * @author Jakub Kubrynski * @author Dave Syer * @author Phillip Webb + * @author Tomasz Przybyla */ public class ApplicationPidFileWriterTests { @@ -56,10 +59,14 @@ public class ApplicationPidFileWriterTests { @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Rule + public ExpectedException exception = ExpectedException.none(); + @Before @After public void resetListener() { System.clearProperty("PIDFILE"); + System.clearProperty("PID_FAIL_ON_WRITE_ERROR"); ApplicationPidFileWriter.reset(); } @@ -85,14 +92,8 @@ public class ApplicationPidFileWriterTests { @Test public void overridePidFileWithSpring() throws Exception { File file = this.temporaryFolder.newFile(); - ConfigurableEnvironment environment = new StandardEnvironment(); - MockPropertySource propertySource = new MockPropertySource(); - propertySource.setProperty("spring.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); + SpringApplicationEvent event = createPreparedEvent("spring.pidfile", + file.getAbsolutePath()); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(); listener.onApplicationEvent(event); assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString())); @@ -101,12 +102,8 @@ public class ApplicationPidFileWriterTests { @Test public void differentEventTypes() throws Exception { File file = this.temporaryFolder.newFile(); - ConfigurableEnvironment environment = new StandardEnvironment(); - MockPropertySource propertySource = new MockPropertySource(); - propertySource.setProperty("spring.pidfile", file.getAbsolutePath()); - environment.getPropertySources().addLast(propertySource); - ApplicationEnvironmentPreparedEvent event = new ApplicationEnvironmentPreparedEvent( - new SpringApplication(), new String[] {}, environment); + SpringApplicationEvent event = createEnvironmentPreparedEvent("spring.pidfile", + file.getAbsolutePath()); ApplicationPidFileWriter listener = new ApplicationPidFileWriter(); listener.onApplicationEvent(event); assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString()); @@ -125,4 +122,64 @@ public class ApplicationPidFileWriterTests { assertThat(FileCopyUtils.copyToString(new FileReader(file)), not(isEmptyString())); } + @Test + public void continueWhenPidFileIsReadOnly() throws Exception { + File file = this.temporaryFolder.newFile(); + file.setReadOnly(); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + listener.onApplicationEvent(EVENT); + assertThat(FileCopyUtils.copyToString(new FileReader(file)), isEmptyString()); + } + + @Test + public void throwWhenPidFileIsReadOnly() throws Exception { + File file = this.temporaryFolder.newFile(); + file.setReadOnly(); + System.setProperty("PID_FAIL_ON_WRITE_ERROR", "true"); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + this.exception.expect(IllegalStateException.class); + this.exception.expectMessage("Cannot create pid file"); + listener.onApplicationEvent(EVENT); + } + + @Test + public void throwWhenPidFileIsReadOnlyWithSpring() throws Exception { + File file = this.temporaryFolder.newFile(); + file.setReadOnly(); + SpringApplicationEvent event = createPreparedEvent( + "spring.pid.fail-on-write-error", "true"); + ApplicationPidFileWriter listener = new ApplicationPidFileWriter(file); + this.exception.expect(IllegalStateException.class); + this.exception.expectMessage("Cannot create pid file"); + listener.onApplicationEvent(event); + } + + private SpringApplicationEvent createEnvironmentPreparedEvent(String propName, + String propValue) { + ConfigurableEnvironment environment = createEnvironment(propName, propValue); + return new ApplicationEnvironmentPreparedEvent(new SpringApplication(), + new String[] {}, environment); + } + + private SpringApplicationEvent createPreparedEvent(String propName, String propValue) { + ConfigurableEnvironment environment = createEnvironment(propName, propValue); + ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); + given(context.getEnvironment()).willReturn(environment); + return new ApplicationPreparedEvent(new SpringApplication(), new String[] {}, + context); + } + + private ConfigurableEnvironment createEnvironment(String propName, String propValue) { + MockPropertySource propertySource = mockPropertySource(propName, propValue); + ConfigurableEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast(propertySource); + return environment; + } + + private MockPropertySource mockPropertySource(String name, String value) { + MockPropertySource propertySource = new MockPropertySource(); + propertySource.setProperty(name, value); + return propertySource; + } + } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index d81190c560..b3072497dd 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -612,7 +612,8 @@ content into your application; rather pick only the properties that you need. management.security.sessions=stateless # session creating policy to use (always, never, if_required, stateless) # PID FILE ({sc-spring-boot-actuator}/system/ApplicationPidFileWriter.{sc-ext}[ApplicationPidFileWriter]) - spring.pidfile= # Location of the PID file to write + spring.pid.file= # Location of the PID file to write + spring.pid.fail-on-write-error= # Fail if the PID file cannot be written # ENDPOINTS ({sc-spring-boot-actuator}/endpoint/AbstractEndpoint.{sc-ext}[AbstractEndpoint] subclasses) endpoints.autoconfig.id=autoconfig