Browse Source

Merge branch '1.5.x'

pull/6883/merge
Andy Wilkinson 9 years ago
parent
commit
b6e8a280fd
  1. 39
      spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc
  2. 160
      spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
  3. 39
      spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
  4. 1
      spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/bar.properties
  5. 1
      spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/loader.properties
  6. 1
      spring-boot-tools/spring-boot-loader/src/test/resources/home/loader.properties
  7. 2
      spring-boot-tools/spring-boot-loader/src/test/resources/placeholders/META-INF/MANIFEST.MF
  8. 1
      spring-boot-tools/spring-boot-loader/src/test/resources/placeholders/loader.properties

39
spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc

@ -147,7 +147,7 @@ files in directories (as opposed to explicitly on the classpath). In the case of
you just add extra jars in those locations if you want more. The `PropertiesLauncher` you just add extra jars in those locations if you want more. The `PropertiesLauncher`
looks in `BOOT-INF/lib/` in your application archive by default, but you can add looks in `BOOT-INF/lib/` in your application archive by default, but you can add
additional locations by setting an environment variable `LOADER_PATH` or `loader.path` additional locations by setting an environment variable `LOADER_PATH` or `loader.path`
in `application.properties` (comma-separated list of directories or archives). in `loader.properties` (comma-separated list of directories or archives).
@ -198,7 +198,13 @@ the appropriate launcher:
`PropertiesLauncher` has a few special features that can be enabled with external `PropertiesLauncher` has a few special features that can be enabled with external
properties (System properties, environment variables, manifest entries or properties (System properties, environment variables, manifest entries or
`application.properties`). `loader.properties`).
NOTE: `PropertiesLauncher` supports loading properties from
`loader.properties` and also (for historic reasons)
`application.properties`. We recommend using
`loader.properties` exclusively, as support for
`application.properties` is deprecated and may be removed in the future.
|=== |===
|Key |Purpose |Key |Purpose
@ -208,8 +214,10 @@ properties (System properties, environment variables, manifest entries or
just like a regular `-classpath` on the `javac` command line. just like a regular `-classpath` on the `javac` command line.
|`loader.home` |`loader.home`
|Location of additional properties file, e.g. `file:///opt/app` |Used to resolve relative paths in `loader.path`. E.g. `loader.path=lib` then
(defaults to `${user.dir}`) `${loader.home}/lib` is a classpath location (along with all jar files in that
directory). Also used to locate a `loader.properties file`. Example `file:///opt/app`
(defaults to `${user.dir}`).
|`loader.args` |`loader.args`
|Default arguments for the main method (space separated) |Default arguments for the main method (space separated)
@ -218,11 +226,11 @@ properties (System properties, environment variables, manifest entries or
|Name of main class to launch, e.g. `com.app.Application`. |Name of main class to launch, e.g. `com.app.Application`.
|`loader.config.name` |`loader.config.name`
|Name of properties file, e.g. `loader` (defaults to `application`). |Name of properties file, e.g. `launcher` (defaults to `loader`).
|`loader.config.location` |`loader.config.location`
|Path to properties file, e.g. `classpath:loader.properties` (defaults to |Path to properties file, e.g. `classpath:loader.properties` (defaults to
`application.properties`). `loader.properties`).
|`loader.system` |`loader.system`
|Boolean flag to indicate that all properties should be added to System properties |Boolean flag to indicate that all properties should be added to System properties
@ -241,7 +249,7 @@ be used:
|`LOADER_PATH` |`LOADER_PATH`
|`loader.home` |`loader.home`
| |`Loader-Home`
|`LOADER_HOME` |`LOADER_HOME`
|`loader.args` |`loader.args`
@ -253,11 +261,11 @@ be used:
|`LOADER_MAIN` |`LOADER_MAIN`
|`loader.config.location` |`loader.config.location`
| |`Loader-Config-Location`
|`LOADER_CONFIG_LOCATION` |`LOADER_CONFIG_LOCATION`
|`loader.system` |`loader.system`
| |`Loader-System`
|`LOADER_SYSTEM` |`LOADER_SYSTEM`
|=== |===
@ -266,15 +274,24 @@ TIP: Build plugins automatically move the `Main-Class` attribute to `Start-Class
the fat jar is built. If you are using that, specify the name of the class to launch using the fat jar is built. If you are using that, specify the name of the class to launch using
the `Main-Class` attribute and leave out `Start-Class`. the `Main-Class` attribute and leave out `Start-Class`.
* `loader.home` is the directory location of an additional properties file (overriding * `loader.properties` are searched for in `loader.home` then in the root of the
the default) as long as `loader.config.location` is not specified. classpath, then in `classpath:/BOOT-INF/classes`. The first location that exists is
used.
* `loader.home` is only the directory location of an additional properties file
(overriding the default) as long as `loader.config.location` is not specified.
* `loader.path` can contain directories (scanned recursively for jar and zip files), * `loader.path` can contain directories (scanned recursively for jar and zip files),
archive paths, or wildcard patterns (for the default JVM behavior). archive paths, or wildcard patterns (for the default JVM behavior).
* `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a * `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a
nested one if running from an archive). Because of this `PropertiesLauncher` behaves the nested one if running from an archive). Because of this `PropertiesLauncher` behaves the
same as `JarLauncher` when no additional configuration is provided. same as `JarLauncher` when no additional configuration is provided.
* `loader.path` can not be used to configure the location of `loader.properties` (the
classpath used to search for the latter is the JVM classpath when `PropertiesLauncher`
is launched).
* Placeholder replacement is done from System and environment variables plus the * Placeholder replacement is done from System and environment variables plus the
properties file itself on all values before use. properties file itself on all values before use.
* The search order for properties (where it makes sense to look in more than one place)
is env vars, system properties, `loader.properties`, exploded archive manifest, archive
manifest.

160
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java

@ -142,48 +142,64 @@ public class PropertiesLauncher extends Launcher {
} }
protected File getHomeDirectory() { protected File getHomeDirectory() {
return new File(SystemPropertyUtils try {
.resolvePlaceholders(System.getProperty(HOME, "${user.dir}"))); return new File(getPropertyWithDefault(HOME, "${user.dir}"));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
} }
private void initializeProperties() throws Exception, IOException { private void initializeProperties() throws Exception, IOException {
String config = "classpath:BOOT-INF/classes/" List<String> configs = new ArrayList<String>();
+ SystemPropertyUtils.resolvePlaceholders( if (getProperty(CONFIG_LOCATION) != null) {
SystemPropertyUtils.getProperty(CONFIG_NAME, "application")) configs.add(getProperty(CONFIG_LOCATION));
+ ".properties"; }
config = SystemPropertyUtils.resolvePlaceholders( else {
SystemPropertyUtils.getProperty(CONFIG_LOCATION, config)); String[] names = getPropertyWithDefault(CONFIG_NAME, "loader,application")
InputStream resource = getResource(config); .split(",");
if (resource != null) { for (String name : names) {
log("Found: " + config); configs.add("file:" + getHomeDirectory() + "/" + name + ".properties");
try { configs.add("classpath:" + name + ".properties");
this.properties.load(resource); configs.add("classpath:BOOT-INF/classes/" + name + ".properties");
}
finally {
resource.close();
} }
for (Object key : Collections.list(this.properties.propertyNames())) { }
String text = this.properties.getProperty((String) key); for (String config : configs) {
String value = SystemPropertyUtils.resolvePlaceholders(this.properties, InputStream resource = getResource(config);
text); if (resource != null) {
if (value != null) { debug("Found: " + config);
this.properties.put(key, value); try {
this.properties.load(resource);
}
finally {
resource.close();
} }
}
if (SystemPropertyUtils
.resolvePlaceholders("${" + SET_SYSTEM_PROPERTIES + ":false}")
.equals("true")) {
log("Adding resolved properties to System properties");
for (Object key : Collections.list(this.properties.propertyNames())) { for (Object key : Collections.list(this.properties.propertyNames())) {
String value = this.properties.getProperty((String) key); if (config.endsWith("application.properties")
System.setProperty((String) key, value); && ((String) key).startsWith("loader.")) {
warn("Use of application.properties for PropertiesLauncher is deprecated");
}
String text = this.properties.getProperty((String) key);
String value = SystemPropertyUtils
.resolvePlaceholders(this.properties, text);
if (value != null) {
this.properties.put(key, value);
}
}
if ("true".equals(getProperty(SET_SYSTEM_PROPERTIES))) {
debug("Adding resolved properties to System properties");
for (Object key : Collections.list(this.properties.propertyNames())) {
String value = this.properties.getProperty((String) key);
System.setProperty((String) key, value);
}
} }
// Load the first one we find
return;
}
else {
debug("Not found: " + config);
} }
} }
else {
log("Not found: " + config);
}
} }
private InputStream getResource(String config) throws Exception { private InputStream getResource(String config) throws Exception {
@ -216,13 +232,13 @@ public class PropertiesLauncher extends Launcher {
config = config.substring(1); config = config.substring(1);
} }
config = "/" + config; config = "/" + config;
log("Trying classpath: " + config); debug("Trying classpath: " + config);
return getClass().getResourceAsStream(config); return getClass().getResourceAsStream(config);
} }
private InputStream getFileResource(String config) throws Exception { private InputStream getFileResource(String config) throws Exception {
File file = new File(config); File file = new File(config);
log("Trying file: " + config); debug("Trying file: " + config);
if (file.canRead()) { if (file.canRead()) {
return new FileInputStream(file); return new FileInputStream(file);
} }
@ -278,7 +294,7 @@ public class PropertiesLauncher extends Launcher {
if (path != null) { if (path != null) {
this.paths = parsePathsProperty(path); this.paths = parsePathsProperty(path);
} }
log("Nested archive paths: " + this.paths); debug("Nested archive paths: " + this.paths);
} }
private List<String> parsePathsProperty(String commaSeparatedPaths) { private List<String> parsePathsProperty(String commaSeparatedPaths) {
@ -326,7 +342,7 @@ public class PropertiesLauncher extends Launcher {
String customLoaderClassName = getProperty("loader.classLoader"); String customLoaderClassName = getProperty("loader.classLoader");
if (customLoaderClassName != null) { if (customLoaderClassName != null) {
loader = wrapWithCustomClassLoader(loader, customLoaderClassName); loader = wrapWithCustomClassLoader(loader, customLoaderClassName);
log("Using custom class loader: " + customLoaderClassName); debug("Using custom class loader: " + customLoaderClassName);
} }
return loader; return loader;
} }
@ -354,34 +370,50 @@ public class PropertiesLauncher extends Launcher {
} }
private String getProperty(String propertyKey) throws Exception { private String getProperty(String propertyKey) throws Exception {
return getProperty(propertyKey, null); return getProperty(propertyKey, null, null);
} }
private String getProperty(String propertyKey, String manifestKey) throws Exception { private String getProperty(String propertyKey, String manifestKey) throws Exception {
return getProperty(propertyKey, manifestKey, null);
}
private String getPropertyWithDefault(String propertyKey, String defaultValue)
throws Exception {
return getProperty(propertyKey, null, defaultValue);
}
private String getProperty(String propertyKey, String manifestKey,
String defaultValue) throws Exception {
if (manifestKey == null) { if (manifestKey == null) {
manifestKey = propertyKey.replace('.', '-'); manifestKey = propertyKey.replace('.', '-');
manifestKey = toCamelCase(manifestKey); manifestKey = toCamelCase(manifestKey);
} }
String property = SystemPropertyUtils.getProperty(propertyKey); String property = SystemPropertyUtils.getProperty(propertyKey);
if (property != null) { if (property != null) {
String value = SystemPropertyUtils.resolvePlaceholders(property); String value = SystemPropertyUtils.resolvePlaceholders(this.properties,
log("Property '" + propertyKey + "' from environment: " + value); property);
debug("Property '" + propertyKey + "' from environment: " + value);
return value; return value;
} }
if (this.properties.containsKey(propertyKey)) { if (this.properties.containsKey(propertyKey)) {
String value = SystemPropertyUtils String value = SystemPropertyUtils.resolvePlaceholders(this.properties,
.resolvePlaceholders(this.properties.getProperty(propertyKey)); this.properties.getProperty(propertyKey));
log("Property '" + propertyKey + "' from properties: " + value); debug("Property '" + propertyKey + "' from properties: " + value);
return value; return value;
} }
try { try {
// Prefer home dir for MANIFEST if there is one if (this.home != null) {
Manifest manifest = new ExplodedArchive(this.home, false).getManifest(); // Prefer home dir for MANIFEST if there is one
if (manifest != null) { Manifest manifest = new ExplodedArchive(this.home, false).getManifest();
String value = manifest.getMainAttributes().getValue(manifestKey); if (manifest != null) {
log("Property '" + manifestKey + "' from home directory manifest: " String value = manifest.getMainAttributes().getValue(manifestKey);
+ value); if (value != null) {
return value; debug("Property '" + manifestKey
+ "' from home directory manifest: " + value);
return SystemPropertyUtils.resolvePlaceholders(this.properties,
value);
}
}
} }
} }
catch (IllegalStateException ex) { catch (IllegalStateException ex) {
@ -392,11 +424,12 @@ public class PropertiesLauncher extends Launcher {
if (manifest != null) { if (manifest != null) {
String value = manifest.getMainAttributes().getValue(manifestKey); String value = manifest.getMainAttributes().getValue(manifestKey);
if (value != null) { if (value != null) {
log("Property '" + manifestKey + "' from archive manifest: " + value); debug("Property '" + manifestKey + "' from archive manifest: " + value);
return value; return SystemPropertyUtils.resolvePlaceholders(this.properties, value);
} }
} }
return null; return defaultValue == null ? defaultValue
: SystemPropertyUtils.resolvePlaceholders(this.properties, defaultValue);
} }
@Override @Override
@ -427,18 +460,18 @@ public class PropertiesLauncher extends Launcher {
file = new File(this.home, root); file = new File(this.home, root);
} }
if (file.isDirectory()) { if (file.isDirectory()) {
log("Adding classpath entries from " + file); debug("Adding classpath entries from " + file);
Archive archive = new ExplodedArchive(file, false); Archive archive = new ExplodedArchive(file, false);
lib.add(archive); lib.add(archive);
} }
Archive archive = getArchive(file); Archive archive = getArchive(file);
if (archive != null) { if (archive != null) {
log("Adding classpath entries from archive " + archive.getUrl() + root); debug("Adding classpath entries from archive " + archive.getUrl() + root);
lib.add(archive); lib.add(archive);
} }
Archive nested = getNestedArchive(root); Archive nested = getNestedArchive(root);
if (nested != null) { if (nested != null) {
log("Adding classpath entries from nested " + nested.getUrl() + root); debug("Adding classpath entries from nested " + root);
lib.add(nested); lib.add(nested);
} }
return lib; return lib;
@ -540,14 +573,21 @@ public class PropertiesLauncher extends Launcher {
return Character.toUpperCase(str.charAt(0)) + str.substring(1); return Character.toUpperCase(str.charAt(0)) + str.substring(1);
} }
private void log(String message) { private void debug(String message) {
if (Boolean.getBoolean(DEBUG)) { if (Boolean.getBoolean(DEBUG)) {
// We shouldn't use java.util.logging because of classpath issues so we log(message);
// just sysout log messages when "loader.debug" is true
System.out.println(message);
} }
} }
private void warn(String message) {
log("WARNING: " + message);
}
private void log(String message) {
// We shouldn't use java.util.logging because of classpath issues
System.out.println(message);
}
/** /**
* Convenience class for finding nested archives that have a prefix in their file path * Convenience class for finding nested archives that have a prefix in their file path
* (e.g. "lib/"). * (e.g. "lib/").

39
spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2016 the original author or authors. * Copyright 2012-2017 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -31,6 +31,7 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@ -50,6 +51,9 @@ public class PropertiesLauncherTests {
@Rule @Rule
public InternalOutputCapture output = new InternalOutputCapture(); public InternalOutputCapture output = new InternalOutputCapture();
@Rule
public ExpectedException expected = ExpectedException.none();
@Rule @Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder(); public TemporaryFolder temporaryFolder = new TemporaryFolder();
@ -72,9 +76,28 @@ public class PropertiesLauncherTests {
@Test @Test
public void testDefaultHome() { public void testDefaultHome() {
System.clearProperty("loader.home");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(launcher.getHomeDirectory())
.isEqualTo(new File(System.getProperty("user.dir")));
}
@Test
public void testAlternateHome() throws Exception {
System.setProperty("loader.home", "src/test/resources/home");
PropertiesLauncher launcher = new PropertiesLauncher(); PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(launcher.getHomeDirectory()) assertThat(launcher.getHomeDirectory())
.isEqualTo(new File(System.getProperty("loader.home"))); .isEqualTo(new File(System.getProperty("loader.home")));
assertThat(launcher.getMainClass()).isEqualTo("demo.HomeApplication");
}
@Test
public void testNonExistentHome() throws Exception {
System.setProperty("loader.home", "src/test/resources/nonexistent");
this.expected.expectMessage("Invalid source folder");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(launcher.getHomeDirectory())
.isNotEqualTo(new File(System.getProperty("loader.home")));
} }
@Test @Test
@ -93,6 +116,13 @@ public class PropertiesLauncherTests {
.isEqualTo("[etc/]"); .isEqualTo("[etc/]");
} }
@Test
public void testRootOfClasspathFirst() throws Exception {
System.setProperty("loader.config.name", "bar");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(launcher.getMainClass()).isEqualTo("my.BarApplication");
}
@Test @Test
public void testUserSpecifiedDotPath() throws Exception { public void testUserSpecifiedDotPath() throws Exception {
System.setProperty("loader.path", "."); System.setProperty("loader.path", ".");
@ -232,6 +262,13 @@ public class PropertiesLauncherTests {
.containsExactly("/foo.jar", "/bar/"); .containsExactly("/foo.jar", "/bar/");
} }
@Test
public void testManifestWithPlaceholders() throws Exception {
System.setProperty("loader.home", "src/test/resources/placeholders");
PropertiesLauncher launcher = new PropertiesLauncher();
assertThat(launcher.getMainClass()).isEqualTo("demo.FooApplication");
}
private void waitFor(String value) throws Exception { private void waitFor(String value) throws Exception {
int count = 0; int count = 0;
boolean timeout = false; boolean timeout = false;

1
spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/bar.properties

@ -0,0 +1 @@
loader.main: my.BootInfBarApplication

1
spring-boot-tools/spring-boot-loader/src/test/resources/BOOT-INF/classes/loader.properties

@ -0,0 +1 @@
loader.main: demo.Application

1
spring-boot-tools/spring-boot-loader/src/test/resources/home/loader.properties

@ -0,0 +1 @@
loader.main: demo.HomeApplication

2
spring-boot-tools/spring-boot-loader/src/test/resources/placeholders/META-INF/MANIFEST.MF

@ -0,0 +1,2 @@
Manifest-Version: 1.0
Start-Class: ${foo.main}

1
spring-boot-tools/spring-boot-loader/src/test/resources/placeholders/loader.properties

@ -0,0 +1 @@
foo.main: demo.FooApplication
Loading…
Cancel
Save