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