From f83fd471846736ee7cf6c440577c77759a50a0dd Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Thu, 19 Sep 2013 16:19:13 +0100 Subject: [PATCH] Add PropertiesLauncher --- .../it/executable-dir/application.properties | 2 + .../src/it/executable-dir/pom.xml | 87 ++++++ .../launcher/it/jar/EmbeddedJarStarter.java | 47 +++ .../launcher/it/jar/ExampleController.java | 37 +++ .../launcher/it/jar/SpringConfiguration.java | 33 ++ .../springframework/boot/loader/Launcher.java | 4 + .../boot/loader/PropertiesLauncher.java | 295 ++++++++++++++++++ .../util/PropertyPlaceholderHelper.java | 253 +++++++++++++++ .../boot/loader/util/SystemPropertyUtils.java | 114 +++++++ 9 files changed, 872 insertions(+) create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-dir/application.properties create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-dir/pom.xml create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java create mode 100644 spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java create mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java create mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/PropertyPlaceholderHelper.java create mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-dir/application.properties b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/application.properties new file mode 100644 index 0000000000..42acafa985 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/application.properties @@ -0,0 +1,2 @@ +loader.path: target,target/lib,. +loader.main: org.springframework.boot.load.it.jar.EmbeddedJarStarter \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-dir/pom.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/pom.xml new file mode 100644 index 0000000000..7442617611 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + org.springframework.boot.launcher.it + executable-dir + 0.0.1.BUILD-SNAPSHOT + jar + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.6 + + + unpack + prepare-package + + copy + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + jar + + + ${project.build.directory}/lib + + + + copy + prepare-package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + java + + -cp + ${project.build.directory}/lib/@project.artifactId@-@project.version@.jar + org.springframework.boot.loader.PropertiesLauncher + + + + + + + + org.eclipse.jetty + jetty-webapp + 8.1.8.v20121106 + + + org.eclipse.jetty + jetty-annotations + 8.1.8.v20121106 + + + org.springframework + spring-webmvc + 3.2.0.RELEASE + + + diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java new file mode 100644 index 0000000000..dbac1f7b63 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/EmbeddedJarStarter.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.load.it.jar; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * Main class to start the embedded server. + * + * @author Phillip Webb + */ +public final class EmbeddedJarStarter { + + public static void main(String[] args) throws Exception { + Server server = new Server(8080); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + + AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext(); + webApplicationContext.register(SpringConfiguration.class); + DispatcherServlet dispatcherServlet = new DispatcherServlet(webApplicationContext); + context.addServlet(new ServletHolder(dispatcherServlet), "/*"); + + server.start(); + server.join(); + } +} diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java new file mode 100644 index 0000000000..e81cda737e --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/ExampleController.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.load.it.jar; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Simple example Spring MVC Controller. + * + * @author Phillip Webb + */ +@Controller +public class ExampleController { + + @RequestMapping("/") + @ResponseBody + public String helloWorld() { + return "Hello Embedded Jar World!"; + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java new file mode 100644 index 0000000000..454dcab9d6 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-dir/src/main/java/org/springframework/launcher/it/jar/SpringConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.load.it.jar; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * Spring configuration. + * + * @author Phillip Webb + */ +@Configuration +@EnableWebMvc +@ComponentScan +public class SpringConfiguration { + +} 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 8ab325434f..b63865be2b 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,4 +195,8 @@ 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 new file mode 100644 index 0000000000..b05f3b26cb --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -0,0 +1,295 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +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 + * application.properties either on the current classpath or in the current + * working directory. The name of the properties file can be changed by setting a System + * property loader.config.name (e.g. -Dloader.config.name=foo + * 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 + * System properties in case the file doesn't exist): + * + *

+ *

+ * + *

+ * + *

+ * + * @author Dave Syer + */ +public class PropertiesLauncher extends Launcher { + + /** + * Properties key for main class + */ + public static final String MAIN = "loader.main"; + + /** + * Properties key for classpath entries (directories possibly containing jars) + */ + public static final String PATH = "loader.path"; + + public static final String HOME = "loader.home"; + + public static String CONFIG_NAME = "loader.config.name"; + + public static String CONFIG_LOCATION = "loader.config.location"; + + private Logger logger = Logger.getLogger(Launcher.class.getName()); + + private static final List DEFAULT_PATHS = Arrays.asList("lib/"); + + private List paths = new ArrayList(DEFAULT_PATHS); + + 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; + } + + @Override + protected void postProcessLib(Archive archive, List lib) throws Exception { + lib.add(0, archive); + } + + /** + * 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) + */ + @Override + protected void launch(String[] args, Archive archive) throws Exception { + initialize(); + super.launch(args, archive); + } + + protected void initialize() throws Exception { + 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); + 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; + } + } + } + } + } + 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, true); + 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()); + } + } + } + finally { + resource.close(); + } + } + else { + this.logger.info("Not found: " + config); + } + for (int i = 0; i < this.paths.size(); i++) { + if (this.paths.get(i).startsWith("./")) { + this.paths.set(i, this.paths.get(i).substring(2)); + } + } + for (Iterator iter = this.paths.iterator(); iter.hasNext();) { + String path = iter.next(); + if (path.equals(".") || path.equals("")) { + iter.remove(); + } + } + this.logger.info("Nested archive paths: " + this.paths); + } + + 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; + } + else if (code == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + } + if (con.getContentLength() >= 0) { + return true; + } + if (httpCon != null) { + // no HTTP OK status, and no content-length header: give up + httpCon.disconnect(); + } + return false; + + } + + @Override + protected String getMainClass(Archive archive) throws Exception { + if (System.getProperty(MAIN) != null) { + return SystemPropertyUtils + .resolvePlaceholders(System.getProperty(MAIN), true); + } + if (this.properties.containsKey(MAIN)) { + return SystemPropertyUtils.resolvePlaceholders( + this.properties.getProperty(MAIN), true); + } + return super.getMainClass(archive); + } + + public static void main(String[] args) { + new PropertiesLauncher().launch(args); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/PropertyPlaceholderHelper.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/PropertyPlaceholderHelper.java new file mode 100644 index 0000000000..61634d295b --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/PropertyPlaceholderHelper.java @@ -0,0 +1,253 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader.util; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * Utility class for working with Strings that have placeholder values in them. A + * placeholder takes the form {@code $ name} . Using {@code PropertyPlaceholderHelper} + * these placeholders can be substituted for user-supplied values. + *

+ * Values for substitution can be supplied using a {@link Properties} instance or using a + * {@link PlaceholderResolver}. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @since 3.0 + */ +public class PropertyPlaceholderHelper { + + private static final Map wellKnownSimplePrefixes = new HashMap( + 4); + + static { + wellKnownSimplePrefixes.put("}", "{"); + wellKnownSimplePrefixes.put("]", "["); + wellKnownSimplePrefixes.put(")", "("); + } + + private final String placeholderPrefix; + + private final String placeholderSuffix; + + private final String simplePrefix; + + private final String valueSeparator; + + private final boolean ignoreUnresolvablePlaceholders; + + /** + * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and + * suffix. Unresolvable placeholders are ignored. + * @param placeholderPrefix the prefix that denotes the start of a placeholder. + * @param placeholderSuffix the suffix that denotes the end of a placeholder. + */ + public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) { + this(placeholderPrefix, placeholderSuffix, null, true); + } + + /** + * Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and + * suffix. + * @param placeholderPrefix the prefix that denotes the start of a placeholder + * @param placeholderSuffix the suffix that denotes the end of a placeholder + * @param valueSeparator the separating character between the placeholder variable and + * the associated default value, if any + * @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders + * should be ignored ({@code true}) or cause an exception ({@code false}). + */ + public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, + String valueSeparator, boolean ignoreUnresolvablePlaceholders) { + + Assert.notNull(placeholderPrefix, "placeholderPrefix must not be null"); + Assert.notNull(placeholderSuffix, "placeholderSuffix must not be null"); + this.placeholderPrefix = placeholderPrefix; + this.placeholderSuffix = placeholderSuffix; + String simplePrefixForSuffix = wellKnownSimplePrefixes + .get(this.placeholderSuffix); + if (simplePrefixForSuffix != null + && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) { + this.simplePrefix = simplePrefixForSuffix; + } + else { + this.simplePrefix = this.placeholderPrefix; + } + this.valueSeparator = valueSeparator; + this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + } + + /** + * Replaces all placeholders of format ${name} with the corresponding + * property from the supplied {@link Properties}. + * @param value the value containing the placeholders to be replaced. + * @param properties the {@code Properties} to use for replacement. + * @return the supplied value with placeholders replaced inline. + */ + public String replacePlaceholders(String value, final Properties properties) { + Assert.notNull(properties, "Argument 'properties' must not be null."); + return replacePlaceholders(value, new PlaceholderResolver() { + @Override + public String resolvePlaceholder(String placeholderName) { + return properties.getProperty(placeholderName); + } + }); + } + + /** + * 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. + * @param placeholderResolver the {@code PlaceholderResolver} to use for replacement. + * @return the supplied value with placeholders replaced inline. + */ + public String replacePlaceholders(String value, + PlaceholderResolver placeholderResolver) { + Assert.notNull(value, "Argument 'value' must not be null."); + return parseStringValue(value, placeholderResolver, new HashSet()); + } + + protected String parseStringValue(String strVal, + PlaceholderResolver placeholderResolver, Set visitedPlaceholders) { + + StringBuilder buf = new StringBuilder(strVal); + + int startIndex = strVal.indexOf(this.placeholderPrefix); + while (startIndex != -1) { + int endIndex = findPlaceholderEndIndex(buf, startIndex); + if (endIndex != -1) { + String placeholder = buf.substring( + startIndex + this.placeholderPrefix.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(placeholder, placeholderResolver, + visitedPlaceholders); + // Now obtain the value for the fully resolved key... + String propVal = placeholderResolver.resolvePlaceholder(placeholder); + if (propVal == null && this.valueSeparator != null) { + int separatorIndex = placeholder.indexOf(this.valueSeparator); + if (separatorIndex != -1) { + String actualPlaceholder = placeholder.substring(0, + separatorIndex); + String defaultValue = placeholder.substring(separatorIndex + + this.valueSeparator.length()); + propVal = placeholderResolver + .resolvePlaceholder(actualPlaceholder); + if (propVal == null) { + propVal = defaultValue; + } + } + } + if (propVal != null) { + // Recursive invocation, parsing placeholders contained in the + // previously resolved placeholder value. + propVal = parseStringValue(propVal, placeholderResolver, + visitedPlaceholders); + buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), + propVal); + startIndex = buf.indexOf(this.placeholderPrefix, + startIndex + propVal.length()); + } + else if (this.ignoreUnresolvablePlaceholders) { + // Proceed with unprocessed value. + startIndex = buf.indexOf(this.placeholderPrefix, endIndex + + this.placeholderSuffix.length()); + } + else { + throw new IllegalArgumentException("Could not resolve placeholder '" + + placeholder + "'" + " in string value \"" + strVal + "\""); + } + visitedPlaceholders.remove(originalPlaceholder); + } + else { + startIndex = -1; + } + } + + return buf.toString(); + } + + private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { + int index = startIndex + this.placeholderPrefix.length(); + int withinNestedPlaceholder = 0; + while (index < buf.length()) { + if (substringMatch(buf, index, this.placeholderSuffix)) { + if (withinNestedPlaceholder > 0) { + withinNestedPlaceholder--; + index = index + this.placeholderSuffix.length(); + } + else { + return index; + } + } + else if (substringMatch(buf, index, this.simplePrefix)) { + withinNestedPlaceholder++; + index = index + this.simplePrefix.length(); + } + else { + index++; + } + } + return -1; + } + + /** + * Strategy interface used to resolve replacement values for placeholders contained in + * Strings. + * @see PropertyPlaceholderHelper + */ + public static interface PlaceholderResolver { + + /** + * Resolves the supplied placeholder name into the replacement value. + * @param placeholderName the name of the placeholder to resolve + * @return the replacement value or {@code null} if no replacement is to be made + */ + String resolvePlaceholder(String placeholderName); + } + + public 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; + } + + public static class Assert { + + public static void notNull(Object target, String message) { + if (target == null) { + throw new IllegalStateException(message); + } + } + + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000000..c69a985fc6 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader.util; + +import org.springframework.boot.loader.util.PropertyPlaceholderHelper.PlaceholderResolver; + +/** + * Helper class for resolving placeholders in texts. Usually applied to file paths. + * + *

+ * A text may contain {@code $ ...}} placeholders, to be resolved as system properties: + * e.g. {@code $ user.dir}}. Default values can be supplied using the ":" separator + * between key and value. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Dave Syer + * @since 1.2.5 + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX + * @see System#getProperty(String) + */ +public abstract class SystemPropertyUtils { + + /** Prefix for system property placeholders: "${" */ + public static final String PLACEHOLDER_PREFIX = "${"; + + /** Suffix for system property placeholders: "}" */ + public static final String PLACEHOLDER_SUFFIX = "}"; + + /** Value separator for system property placeholders: ":" */ + public static final String VALUE_SEPARATOR = ":"; + + private static final PropertyPlaceholderHelper strictHelper = new PropertyPlaceholderHelper( + PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, false); + + private static final PropertyPlaceholderHelper nonStrictHelper = new PropertyPlaceholderHelper( + PLACEHOLDER_PREFIX, PLACEHOLDER_SUFFIX, VALUE_SEPARATOR, true); + + /** + * Resolve ${...} placeholders in the given text, replacing them with corresponding + * system property values. + * @param text the String to resolve + * @return the resolved String + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX + * @throws IllegalArgumentException if there is an unresolvable placeholder + */ + public static String resolvePlaceholders(String text) { + return resolvePlaceholders(text, false); + } + + /** + * Resolve ${...} placeholders in the given text, replacing them with corresponding + * system property values. Unresolvable placeholders with no default value are ignored + * and passed through unchanged if the flag is set to true. + * @param text the String to resolve + * @param ignoreUnresolvablePlaceholders flag to determine is unresolved placeholders + * are ignored + * @return the resolved String + * @see #PLACEHOLDER_PREFIX + * @see #PLACEHOLDER_SUFFIX + * @throws IllegalArgumentException if there is an unresolvable placeholder and the + * flag is false + */ + public static String resolvePlaceholders(String text, + boolean ignoreUnresolvablePlaceholders) { + PropertyPlaceholderHelper helper = (ignoreUnresolvablePlaceholders ? nonStrictHelper + : strictHelper); + return helper.replacePlaceholders(text, new SystemPropertyPlaceholderResolver( + text)); + } + + private static class SystemPropertyPlaceholderResolver implements PlaceholderResolver { + + private final String text; + + public SystemPropertyPlaceholderResolver(String text) { + this.text = text; + } + + @Override + public String resolvePlaceholder(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 [" + this.text + "] as system property: " + ex); + return null; + } + } + } + +}