diff --git a/spring-boot-docs/src/main/asciidoc/deployment.adoc b/spring-boot-docs/src/main/asciidoc/deployment.adoc
index 3794a6265e37..a0966faf1fff 100644
--- a/spring-boot-docs/src/main/asciidoc/deployment.adoc
+++ b/spring-boot-docs/src/main/asciidoc/deployment.adoc
@@ -705,6 +705,11 @@ for Gradle and to `${project.name}` for Maven.
|`confFolder`
|The default value for `CONF_FOLDER`. Defaults to the folder containing the jar.
+|`inlinedConfScript`
+|Reference to a file script that should be inlined in the default launch script.
+ This can be used to set environmental variables such as `JAVA_OPTS` before
+ any external config files are loaded.
+
|`logFolder`
|The default value for `LOG_FOLDER`. Only valid for an `init.d` service.
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLaunchScript.java b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLaunchScript.java
index 3e917e54fe2f..bcbb475874d3 100644
--- a/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLaunchScript.java
+++ b/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/DefaultLaunchScript.java
@@ -16,34 +16,35 @@
package org.springframework.boot.loader.tools;
-import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.springframework.util.FileCopyUtils;
+
/**
* Default implementation of {@link LaunchScript}. Provides the default Spring Boot launch
* script or can load a specific script File. Also support mustache style template
* expansion of the form {{name:default}}
.
*
* @author Phillip Webb
+ * @author Justin Rosenberg
* @since 1.3.0
*/
public class DefaultLaunchScript implements LaunchScript {
private static final Charset UTF_8 = Charset.forName("UTF-8");
- private static final int BUFFER_SIZE = 4096;
-
private static final Pattern PLACEHOLDER_PATTERN = Pattern
.compile("\\{\\{(\\w+)(:.*?)?\\}\\}(?!\\})");
+ private static final List FILE_PATH_KEYS = Arrays.asList("inlinedConfScript");
+
private final String content;
/**
@@ -57,45 +58,52 @@ public DefaultLaunchScript(File file, Map, ?> properties) throws IOException {
this.content = expandPlaceholders(content, properties);
}
+ /**
+ * Loads file contents.
+ * @param file File to load. If null, will load default launch.script
+ * @return String representation of file contents.
+ * @throws IOException if file is not found our can't be loaded.
+ */
private String loadContent(File file) throws IOException {
+ final byte[] fileBytes;
if (file == null) {
- return loadContent(getClass().getResourceAsStream("launch.script"));
+ fileBytes = FileCopyUtils
+ .copyToByteArray(getClass().getResourceAsStream("launch.script"));
}
- return loadContent(new FileInputStream(file));
- }
-
- private String loadContent(InputStream inputStream) throws IOException {
- try {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- copy(inputStream, outputStream);
- return new String(outputStream.toByteArray(), UTF_8);
- }
- finally {
- inputStream.close();
+ else {
+ fileBytes = FileCopyUtils.copyToByteArray(file);
}
+ return new String(fileBytes, UTF_8);
}
- private void copy(InputStream inputStream, OutputStream outputStream)
+ /**
+ * Replaces variable placeholders in file with specified property values.
+ * @param content String with variables defined in {{variable:default}} format.
+ * @param properties Key value pairs for variables to replace
+ * @return Updated String
+ * @throws IOException if a file property value or path is specified and the file
+ * cannot be loaded.
+ */
+ private String expandPlaceholders(String content, Map, ?> properties)
throws IOException {
- byte[] buffer = new byte[BUFFER_SIZE];
- int bytesRead = -1;
- while ((bytesRead = inputStream.read(buffer)) != -1) {
- outputStream.write(buffer, 0, bytesRead);
- }
- outputStream.flush();
- }
-
- private String expandPlaceholders(String content, Map, ?> properties) {
StringBuffer expanded = new StringBuffer();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(content);
while (matcher.find()) {
String name = matcher.group(1);
- String value = matcher.group(2);
+ final String value;
+ String defaultValue = matcher.group(2);
if (properties != null && properties.containsKey(name)) {
- value = (String) properties.get(name);
+ Object propertyValue = properties.get(name);
+ if (FILE_PATH_KEYS.contains(name)) {
+ value = parseFilePropertyValue(properties.get(name));
+ }
+ else {
+ value = propertyValue.toString();
+ }
}
else {
- value = (value == null ? matcher.group(0) : value.substring(1));
+ value = (defaultValue == null ? matcher.group(0)
+ : defaultValue.substring(1));
}
matcher.appendReplacement(expanded, value.replace("$", "\\$"));
}
@@ -103,6 +111,26 @@ private String expandPlaceholders(String content, Map, ?> properties) {
return expanded.toString();
}
+ /**
+ * Loads file based on File object or String path.
+ * @param propertyValue File Object or String path to file.
+ * @return File contents.
+ * @throws IOException if a file property value or path is specified and the file
+ * cannot be loaded.
+ */
+ private String parseFilePropertyValue(Object propertyValue) throws IOException {
+ if (propertyValue instanceof File) {
+ return loadContent((File) propertyValue);
+ }
+ else {
+ return loadContent(new File(propertyValue.toString()));
+ }
+ }
+
+ /**
+ * The content of the launch script as a byte array.
+ * @return Byte representation of script.
+ */
@Override
public byte[] toByteArray() {
return this.content.getBytes(UTF_8);
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script
old mode 100755
new mode 100644
index 25facc1ede11..5e6fa037f846
--- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script
+++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/loader/tools/launch.script
@@ -40,6 +40,9 @@ done
jarfolder="$( (cd "$(dirname "$jarfile")" && pwd -P) )"
cd "$WORKING_DIR" || exit 1
+# Inline script specified in build properties
+{{inlinedConfScript:}}
+
# Source any config file
configfile="$(basename "${jarfile%.*}.conf")"
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java
index 2582cba95c81..f5661840d371 100644
--- a/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java
+++ b/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/DefaultLaunchScriptTests.java
@@ -17,6 +17,7 @@
package org.springframework.boot.loader.tools;
import java.io.File;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -126,6 +127,14 @@ public void stopWaitTimeCanBeReplaced() throws Exception {
assertThatPlaceholderCanBeReplaced("stopWaitTime");
}
+ @Test
+ public void inlinedConfScriptFileLoad() throws IOException {
+ DefaultLaunchScript script = new DefaultLaunchScript(null,
+ createProperties("inlinedConfScript:src/test/resources/example.script"));
+ String content = new String(script.toByteArray());
+ assertThat(content).contains("FOO=BAR");
+ }
+
@Test
public void defaultForUseStartStopDaemonIsTrue() throws Exception {
DefaultLaunchScript script = new DefaultLaunchScript(null, null);
@@ -185,6 +194,15 @@ public void expandVariablesWithDefaults() throws Exception {
assertThat(content).isEqualTo("hello");
}
+ @Test
+ public void expandVariablesCanDefaultToBlank() throws Exception {
+ File file = this.temporaryFolder.newFile();
+ FileCopyUtils.copy("s{{p:}}{{r:}}ing".getBytes(), file);
+ DefaultLaunchScript script = new DefaultLaunchScript(file, null);
+ String content = new String(script.toByteArray());
+ assertThat(content).isEqualTo("sing");
+ }
+
@Test
public void expandVariablesWithDefaultsOverride() throws Exception {
File file = this.temporaryFolder.newFile();
diff --git a/spring-boot-tools/spring-boot-loader-tools/src/test/resources/example.script b/spring-boot-tools/spring-boot-loader-tools/src/test/resources/example.script
new file mode 100644
index 000000000000..15c36f50ef73
--- /dev/null
+++ b/spring-boot-tools/spring-boot-loader-tools/src/test/resources/example.script
@@ -0,0 +1 @@
+FOO=BAR
\ No newline at end of file