From e9fd7c96b80fbab0d9116779655a63162b386db7 Mon Sep 17 00:00:00 2001
From: Phillip Webb
Date: Thu, 19 Sep 2013 10:51:15 -0700
Subject: [PATCH] Loader changes
---
.../springframework/boot/loader/Launcher.java | 4 -
.../boot/loader/PropertiesLauncher.java | 302 +++++++++---------
.../boot/loader/util/SystemPropertyUtils.java | 215 ++++++-------
.../boot/loader/PropertiesLauncherTests.java | 5 +-
4 files changed, 249 insertions(+), 277 deletions(-)
diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java
index b63865be2b..8ab325434f 100644
--- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java
+++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java
@@ -195,8 +195,4 @@ public abstract class Launcher {
return (Runnable) constructor.newInstance(mainClass, args);
}
- protected boolean isArchive(String name) {
- return name.endsWith(".jar") || name.endsWith(".zip");
- }
-
}
diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
index a68b11546b..4f74a2597d 100644
--- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
+++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java
@@ -23,22 +23,19 @@ import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
+import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import org.springframework.boot.loader.util.SystemPropertyUtils;
-import org.springframework.util.ResourceUtils;
/**
- *
* {@link Launcher} for archives with user-configured classpath and main class via a
* properties file. This model is often more flexible and more amenable to creating
* well-behaved OS-level services than a model based on executable jars.
- *
*
*
* Looks in various places for a properties file to extract loader settings, defaulting to
@@ -48,7 +45,7 @@ import org.springframework.util.ResourceUtils;
* will look for foo.properties
. If that file doesn't exist then tries
* loader.config.location
(with allowed prefixes classpath:
and
* file:
or any valid URL). Once that file is located turns it into
- * Properties and extracts optional values (which can also be provided oroverridden as
+ * Properties and extracts optional values (which can also be provided overridden as
* System properties in case the file doesn't exist):
*
*
@@ -59,16 +56,13 @@ import org.springframework.util.ResourceUtils;
* loader is set up. No default, but will fall back to looking in a
* MANIFEST.MF
if there is one.
*
- *
- *
- *
- *
- *
*
* @author Dave Syer
*/
public class PropertiesLauncher extends Launcher {
+ private Logger logger = Logger.getLogger(Launcher.class.getName());
+
/**
* Properties key for main class
*/
@@ -81,11 +75,9 @@ public class PropertiesLauncher extends Launcher {
public static final String HOME = "loader.home";
- public static String CONFIG_NAME = "loader.config.name";
-
- public static String CONFIG_LOCATION = "loader.config.location";
+ public static final String CONFIG_NAME = "loader.config.name";
- private Logger logger = Logger.getLogger(Launcher.class.getName());
+ public static final String CONFIG_LOCATION = "loader.config.location";
private static final List DEFAULT_PATHS = Arrays.asList("lib/");
@@ -94,51 +86,20 @@ public class PropertiesLauncher extends Launcher {
private Properties properties = new Properties();
@Override
- public void launch(String[] args) {
- try {
- launch(args, new ExplodedArchive(new File(getHomeDirectory())));
- }
- catch (Exception ex) {
- ex.printStackTrace();
- System.exit(1);
- }
- }
-
- protected String getHomeDirectory() {
- return SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
- "${user.dir}"));
- }
-
- @Override
- protected boolean isNestedArchive(Archive.Entry entry) {
- String name = entry.getName();
- if (entry.isDirectory()) {
- for (String path : this.paths) {
- if (path.length() > 0 && name.equals(path)) {
- return true;
- }
- }
- }
- else {
- for (String path : this.paths) {
- if (path.length() > 0 && name.startsWith(path) && isArchive(name)) {
- return true;
- }
- }
- }
- return false;
+ protected void launch(String[] args, ProtectionDomain protectionDomain)
+ throws Exception {
+ launch(args, new ExplodedArchive(getHomeDirectory()));
}
- @Override
- protected void postProcessLib(Archive archive, List lib) throws Exception {
- lib.add(0, archive);
+ protected File getHomeDirectory() {
+ return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
+ "${user.dir}")));
}
/**
* Look in various places for a properties file to extract loader settings. Default to
* application.properties
either on the current classpath or in the
* current working directory.
- *
* @see org.springframework.boot.loader.Launcher#launch(java.lang.String[],
* org.springframework.boot.loader.Archive)
*/
@@ -149,83 +110,24 @@ public class PropertiesLauncher extends Launcher {
}
protected void initialize() throws Exception {
+ initializeProperties();
+ initializePaths();
+ }
+ private void initializeProperties() throws Exception, IOException {
String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_NAME, "application")) + ".properties";
- while (config.startsWith("/")) {
- config = config.substring(1);
- }
- this.logger.fine("Trying default location: " + config);
- InputStream resource = getClass().getResourceAsStream("/" + config);
+ InputStream resource = getClasspathResource(config);
if (resource == null) {
-
config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_LOCATION, config));
-
- if (config.startsWith("classpath:")) {
-
- config = config.substring("classpath:".length());
- while (config.startsWith("/")) {
- config = config.substring(1);
- }
- config = "/" + config;
- this.logger.fine("Trying classpath: " + config);
- resource = getClass().getResourceAsStream(config);
-
- }
- else {
-
- if (config.startsWith("file:")) {
-
- config = config.substring("file:".length());
- if (config.startsWith("//")) {
- config = config.substring(2);
- }
-
- }
- if (!config.contains(":")) {
-
- File file = new File(config);
- this.logger.fine("Trying file: " + config);
- if (file.canRead()) {
- resource = new FileInputStream(file);
- }
-
- }
- else {
-
- URL url = new URL(config);
- if (exists(url)) {
- URLConnection con = url.openConnection();
- try {
- resource = con.getInputStream();
- }
- catch (IOException ex) {
- // Close the HTTP connection (if applicable).
- if (con instanceof HttpURLConnection) {
- ((HttpURLConnection) con).disconnect();
- }
- throw ex;
- }
- }
- }
- }
+ resource = getResource(config);
}
+
if (resource != null) {
this.logger.info("Found: " + config);
- this.properties.load(resource);
try {
- String path = System.getProperty(PATH);
- if (path == null) {
- path = this.properties.getProperty(PATH);
- }
- if (path != null) {
- path = SystemPropertyUtils.resolvePlaceholders(path);
- this.paths = new ArrayList(Arrays.asList(path.split(",")));
- for (int i = 0; i < this.paths.size(); i++) {
- this.paths.set(i, this.paths.get(i).trim());
- }
- }
+ this.properties.load(resource);
}
finally {
resource.close();
@@ -234,53 +136,153 @@ public class PropertiesLauncher extends Launcher {
else {
this.logger.info("Not found: " + config);
}
- for (int i = 0; i < this.paths.size(); i++) {
- if (!this.paths.get(i).endsWith("/")) {
- // Always a directory
- this.paths.set(i, this.paths.get(i) + "/");
- }
- if (this.paths.get(i).startsWith("./")) {
- // No need for current dir path
- this.paths.set(i, this.paths.get(i).substring(2));
+ }
+
+ private InputStream getResource(String config) throws Exception {
+ if (config.startsWith("classpath:")) {
+ return getClasspathResource(config.substring("classpath:".length()));
+ }
+ config = stripFileUrlPrefix(config);
+ if (isUrl(config)) {
+ return getURLResource(config);
+ }
+ return getFileResource(config);
+ }
+
+ private String stripFileUrlPrefix(String config) {
+ if (config.startsWith("file:")) {
+ config = config.substring("file:".length());
+ if (config.startsWith("//")) {
+ config = config.substring(2);
}
}
- for (Iterator iter = this.paths.iterator(); iter.hasNext();) {
- String path = iter.next();
- if (path.equals(".") || path.equals("")) {
- // Empty path is always on the classpath so no need for it to be
- // explicitly listed here
- iter.remove();
+ return config;
+ }
+
+ private boolean isUrl(String config) {
+ return config.contains("://");
+ }
+
+ private InputStream getClasspathResource(String config) {
+ while (config.startsWith("/")) {
+ config = config.substring(1);
+ }
+ config = "/" + config;
+ this.logger.fine("Trying classpath: " + config);
+ return getClass().getResourceAsStream(config);
+ }
+
+ private InputStream getFileResource(String config) throws Exception {
+ File file = new File(config);
+ this.logger.fine("Trying file: " + config);
+ if (file.canRead()) {
+ return new FileInputStream(file);
+ }
+ return null;
+ }
+
+ private InputStream getURLResource(String config) throws Exception {
+ URL url = new URL(config);
+ if (exists(url)) {
+ URLConnection con = url.openConnection();
+ try {
+ return con.getInputStream();
+ }
+ catch (IOException ex) {
+ // Close the HTTP connection (if applicable).
+ if (con instanceof HttpURLConnection) {
+ ((HttpURLConnection) con).disconnect();
+ }
+ throw ex;
}
}
- this.logger.info("Nested archive paths: " + this.paths);
+ return null;
}
private boolean exists(URL url) throws IOException {
-
// Try a URL connection content-length header...
- URLConnection con = url.openConnection();
- ResourceUtils.useCachesIfNecessary(con);
- HttpURLConnection httpCon = (con instanceof HttpURLConnection ? (HttpURLConnection) con
- : null);
- if (httpCon != null) {
- httpCon.setRequestMethod("HEAD");
- int code = httpCon.getResponseCode();
- if (code == HttpURLConnection.HTTP_OK) {
- return true;
+ URLConnection connection = url.openConnection();
+ try {
+ connection.setUseCaches(connection.getClass().getSimpleName()
+ .startsWith("JNLP"));
+ if (connection instanceof HttpURLConnection) {
+ HttpURLConnection httpConnection = (HttpURLConnection) connection;
+ httpConnection.setRequestMethod("HEAD");
+ int responseCode = httpConnection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ return true;
+ }
+ else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
+ return false;
+ }
}
- else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
- return false;
+ return (connection.getContentLength() >= 0);
+ }
+ finally {
+ if (connection instanceof HttpURLConnection) {
+ ((HttpURLConnection) connection).disconnect();
+ }
+ }
+ }
+
+ private void initializePaths() throws IOException {
+ String path = System.getProperty(PATH);
+ if (path == null) {
+ path = this.properties.getProperty(PATH);
+ }
+ if (path != null) {
+ this.paths = parsePathsProperty(SystemPropertyUtils.resolvePlaceholders(path));
+ }
+ this.logger.info("Nested archive paths: " + this.paths);
+ }
+
+ private List parsePathsProperty(String commaSeparatedPaths) {
+ List paths = new ArrayList();
+ for (String path : commaSeparatedPaths.split(",")) {
+ path = cleanupPath(path);
+ // Empty path is always on the classpath so no need for it to be explicitly
+ // listed here
+ if (!(path.equals(".") || path.equals(""))) {
+ paths.add(path);
}
}
- if (con.getContentLength() >= 0) {
- return true;
+ return paths;
+ }
+
+ private String cleanupPath(String path) {
+ path = path.trim();
+ // Always a directory
+ if (!path.endsWith("/")) {
+ path = path + "/";
}
- if (httpCon != null) {
- // no HTTP OK status, and no content-length header: give up
- httpCon.disconnect();
+ // No need for current dir path
+ if (path.startsWith("./")) {
+ path = path.substring(2);
+ }
+ return path;
+ }
+
+ @Override
+ protected boolean isNestedArchive(Archive.Entry entry) {
+ String name = entry.getName();
+ for (String path : this.paths) {
+ if (path.length() > 0) {
+ if ((entry.isDirectory() && name.equals(path))
+ || (!entry.isDirectory() && name.startsWith(path) && isArchive(name))) {
+ return true;
+ }
+ }
}
return false;
+ }
+
+ private boolean isArchive(String name) {
+ return name.endsWith(".jar") || name.endsWith(".zip");
+ }
+ @Override
+ protected void postProcessLib(Archive archive, List lib) throws Exception {
+ lib.add(0, archive);
}
@Override
diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java
index 9b8a58378a..644c73dc73 100644
--- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java
+++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java
@@ -19,8 +19,6 @@ package org.springframework.boot.loader.util;
import java.util.HashSet;
import java.util.Set;
-import org.springframework.util.PropertyPlaceholderHelper.PlaceholderResolver;
-
/**
* Helper class for resolving placeholders in texts. Usually applied to file paths.
*
@@ -50,7 +48,7 @@ public abstract class SystemPropertyUtils {
/** Value separator for system property placeholders: ":" */
public static final String VALUE_SEPARATOR = ":";
- private static final PropertyPlaceholderHelper helper = new PropertyPlaceholderHelper();
+ private static final String SIMPLE_PREFIX = PLACEHOLDER_PREFIX.substring(1);
/**
* Resolve ${...} placeholders in the given text, replacing them with corresponding
@@ -62,147 +60,120 @@ public abstract class SystemPropertyUtils {
* @throws IllegalArgumentException if there is an unresolvable placeholder
*/
public static String resolvePlaceholders(String text) {
- return helper.replacePlaceholders(text);
- }
-
- static protected class PropertyPlaceholderHelper {
-
- private static final String simplePrefix = PLACEHOLDER_PREFIX.substring(1);
-
- /**
- * Replaces all placeholders of format {@code $ name} with the value returned from
- * the supplied {@link PlaceholderResolver}.
- * @param value the value containing the placeholders to be replaced.
- * @return the supplied value with placeholders replaced inline.
- */
- public String replacePlaceholders(String value) {
- Assert.notNull(value, "Argument 'value' must not be null.");
- return parseStringValue(value, value, new HashSet());
+ if (text == null) {
+ throw new IllegalArgumentException("Argument 'value' must not be null.");
}
+ return parseStringValue(text, text, new HashSet());
+ }
- private String parseStringValue(String value, String current,
- Set visitedPlaceholders) {
-
- StringBuilder buf = new StringBuilder(current);
-
- int startIndex = current.indexOf(PLACEHOLDER_PREFIX);
- while (startIndex != -1) {
- int endIndex = findPlaceholderEndIndex(buf, startIndex);
- if (endIndex != -1) {
- String placeholder = buf.substring(
- startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
- String originalPlaceholder = placeholder;
- if (!visitedPlaceholders.add(originalPlaceholder)) {
- throw new IllegalArgumentException(
- "Circular placeholder reference '" + originalPlaceholder
- + "' in property definitions");
- }
- // Recursive invocation, parsing placeholders contained in the
- // placeholder
- // key.
- placeholder = parseStringValue(value, placeholder,
- visitedPlaceholders);
- // Now obtain the value for the fully resolved key...
- String propVal = resolvePlaceholder(value, placeholder);
- if (propVal == null && VALUE_SEPARATOR != null) {
- int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR);
- if (separatorIndex != -1) {
- String actualPlaceholder = placeholder.substring(0,
- separatorIndex);
- String defaultValue = placeholder.substring(separatorIndex
- + VALUE_SEPARATOR.length());
- propVal = resolvePlaceholder(value, actualPlaceholder);
- if (propVal == null) {
- propVal = defaultValue;
- }
+ private static String parseStringValue(String value, String current,
+ Set visitedPlaceholders) {
+
+ StringBuilder buf = new StringBuilder(current);
+
+ int startIndex = current.indexOf(PLACEHOLDER_PREFIX);
+ while (startIndex != -1) {
+ int endIndex = findPlaceholderEndIndex(buf, startIndex);
+ if (endIndex != -1) {
+ String placeholder = buf.substring(
+ startIndex + PLACEHOLDER_PREFIX.length(), endIndex);
+ String originalPlaceholder = placeholder;
+ if (!visitedPlaceholders.add(originalPlaceholder)) {
+ throw new IllegalArgumentException("Circular placeholder reference '"
+ + originalPlaceholder + "' in property definitions");
+ }
+ // Recursive invocation, parsing placeholders contained in the
+ // placeholder
+ // key.
+ placeholder = parseStringValue(value, placeholder, visitedPlaceholders);
+ // Now obtain the value for the fully resolved key...
+ String propVal = resolvePlaceholder(value, placeholder);
+ if (propVal == null && VALUE_SEPARATOR != null) {
+ int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR);
+ if (separatorIndex != -1) {
+ String actualPlaceholder = placeholder.substring(0,
+ separatorIndex);
+ String defaultValue = placeholder.substring(separatorIndex
+ + VALUE_SEPARATOR.length());
+ propVal = resolvePlaceholder(value, actualPlaceholder);
+ if (propVal == null) {
+ propVal = defaultValue;
}
}
- if (propVal != null) {
- // Recursive invocation, parsing placeholders contained in the
- // previously resolved placeholder value.
- propVal = parseStringValue(value, propVal, visitedPlaceholders);
- buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(),
- propVal);
- startIndex = buf.indexOf(PLACEHOLDER_PREFIX,
- startIndex + propVal.length());
- }
- else {
- // Proceed with unprocessed value.
- startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex
- + PLACEHOLDER_SUFFIX.length());
- }
- visitedPlaceholders.remove(originalPlaceholder);
+ }
+ if (propVal != null) {
+ // Recursive invocation, parsing placeholders contained in the
+ // previously resolved placeholder value.
+ propVal = parseStringValue(value, propVal, visitedPlaceholders);
+ buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(),
+ propVal);
+ startIndex = buf.indexOf(PLACEHOLDER_PREFIX,
+ startIndex + propVal.length());
}
else {
- startIndex = -1;
+ // Proceed with unprocessed value.
+ startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex
+ + PLACEHOLDER_SUFFIX.length());
}
+ visitedPlaceholders.remove(originalPlaceholder);
+ }
+ else {
+ startIndex = -1;
}
-
- return buf.toString();
}
- private String resolvePlaceholder(String text, String placeholderName) {
- try {
- String propVal = System.getProperty(placeholderName);
- if (propVal == null) {
- // Fall back to searching the system environment.
- propVal = System.getenv(placeholderName);
- }
- return propVal;
- }
- catch (Throwable ex) {
- System.err.println("Could not resolve placeholder '" + placeholderName
- + "' in [" + text + "] as system property: " + ex);
- return null;
+ return buf.toString();
+ }
+
+ private static String resolvePlaceholder(String text, String placeholderName) {
+ try {
+ String propVal = System.getProperty(placeholderName);
+ if (propVal == null) {
+ // Fall back to searching the system environment.
+ propVal = System.getenv(placeholderName);
}
+ return propVal;
}
+ catch (Throwable ex) {
+ System.err.println("Could not resolve placeholder '" + placeholderName
+ + "' in [" + text + "] as system property: " + ex);
+ return null;
+ }
+ }
- private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
- int index = startIndex + PLACEHOLDER_PREFIX.length();
- int withinNestedPlaceholder = 0;
- while (index < buf.length()) {
- if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
- if (withinNestedPlaceholder > 0) {
- withinNestedPlaceholder--;
- index = index + PLACEHOLDER_SUFFIX.length();
- }
- else {
- return index;
- }
- }
- else if (substringMatch(buf, index,
- PropertyPlaceholderHelper.simplePrefix)) {
- withinNestedPlaceholder++;
- index = index + PropertyPlaceholderHelper.simplePrefix.length();
+ private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
+ int index = startIndex + PLACEHOLDER_PREFIX.length();
+ int withinNestedPlaceholder = 0;
+ while (index < buf.length()) {
+ if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) {
+ if (withinNestedPlaceholder > 0) {
+ withinNestedPlaceholder--;
+ index = index + PLACEHOLDER_SUFFIX.length();
}
else {
- index++;
+ return index;
}
}
- return -1;
- }
-
- private static boolean substringMatch(CharSequence str, int index,
- CharSequence substring) {
- for (int j = 0; j < substring.length(); j++) {
- int i = index + j;
- if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
- return false;
- }
+ else if (substringMatch(buf, index, SIMPLE_PREFIX)) {
+ withinNestedPlaceholder++;
+ index = index + SIMPLE_PREFIX.length();
+ }
+ else {
+ index++;
}
- return true;
}
+ return -1;
+ }
- private static class Assert {
-
- public static void notNull(Object target, String message) {
- if (target == null) {
- throw new IllegalStateException(message);
- }
+ private static boolean substringMatch(CharSequence str, int index,
+ CharSequence substring) {
+ for (int j = 0; j < substring.length(); j++) {
+ int i = index + j;
+ if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
+ return false;
}
-
}
-
+ return true;
}
}
diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
index aae8fee959..035b87b80c 100644
--- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
+++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java
@@ -16,6 +16,8 @@
package org.springframework.boot.loader;
+import java.io.File;
+
import org.junit.After;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
@@ -40,7 +42,8 @@ public class PropertiesLauncherTests {
@Test
public void testDefaultHome() {
- assertEquals(System.getProperty("user.dir"), this.launcher.getHomeDirectory());
+ assertEquals(new File(System.getProperty("user.dir")),
+ this.launcher.getHomeDirectory());
}
@Test