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): * *

- *

- * - *

- * - *

* * @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