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