diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml
index d03f1885ef9..33cf8056f86 100644
--- a/spring-boot-cli/pom.xml
+++ b/spring-boot-cli/pom.xml
@@ -233,11 +233,14 @@
true
- org.springframework.boot.loader.JarLauncher
+ org.springframework.boot.loader.PropertiesLauncher
${start-class}
+
+ groovy.lang.GroovyClassLoader
+
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 6cf127f639f..ab490ad8306 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
@@ -33,7 +33,11 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
@@ -116,6 +120,8 @@ public class PropertiesLauncher extends Launcher {
private static final List DEFAULT_PATHS = Arrays.asList("lib/");
+ private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
+
private final File home;
private List paths = new ArrayList(DEFAULT_PATHS);
@@ -123,6 +129,9 @@ public class PropertiesLauncher extends Launcher {
private Properties properties = new Properties();
public PropertiesLauncher() {
+ if (!isDebug()) {
+ this.logger.setLevel(Level.SEVERE);
+ }
try {
this.home = getHomeDirectory();
initializeProperties(this.home);
@@ -133,6 +142,22 @@ public class PropertiesLauncher extends Launcher {
}
}
+ private boolean isDebug() {
+ String debug = System.getProperty("debug");
+ if (debug != null && !"false".equals(debug)) {
+ return true;
+ }
+ debug = System.getProperty("DEBUG");
+ if (debug != null && !"false".equals(debug)) {
+ return true;
+ }
+ debug = System.getenv("DEBUG");
+ if (debug != null && !"false".equals(debug)) {
+ return true;
+ }
+ return false;
+ }
+
protected File getHomeDirectory() {
return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
"${user.dir}")));
@@ -290,30 +315,82 @@ public class PropertiesLauncher extends Launcher {
@Override
protected String getMainClass() throws Exception {
- String property = SystemPropertyUtils.getProperty(MAIN);
+ String mainClass = getProperty(MAIN, "Start-Class");
+ if (mainClass == null) {
+ throw new IllegalStateException("No '" + MAIN
+ + "' or 'Start-Class' specified");
+ }
+ return mainClass;
+ }
+
+ @Override
+ protected ClassLoader createClassLoader(List archives) throws Exception {
+ ClassLoader loader = super.createClassLoader(archives);
+ String classLoaderType = getProperty("loader.classLoader");
+ if (classLoaderType != null) {
+ Class> type = Class.forName(classLoaderType, true, loader);
+ try {
+ loader = (ClassLoader) type.getConstructor(ClassLoader.class)
+ .newInstance(loader);
+ }
+ catch (NoSuchMethodException e) {
+ try {
+ loader = (ClassLoader) type.getConstructor(URL[].class,
+ ClassLoader.class).newInstance(new URL[0], loader);
+ }
+ catch (NoSuchMethodException ex) {
+ loader = (ClassLoader) type.newInstance();
+ }
+ }
+ this.logger.info("Using custom class loader: " + classLoaderType);
+ }
+ return loader;
+ }
+
+ private String getProperty(String propertyKey) throws Exception {
+ return getProperty(propertyKey, null);
+ }
+
+ private String getProperty(String propertyKey, String manifestKey) throws Exception {
+ if (manifestKey == null) {
+ manifestKey = propertyKey.replace(".", "-");
+ manifestKey = toCamelCase(manifestKey);
+ }
+ String property = SystemPropertyUtils.getProperty(propertyKey);
if (property != null) {
- String mainClass = SystemPropertyUtils.resolvePlaceholders(property);
- this.logger.info("Main class from environment: " + mainClass);
- return mainClass;
+ String value = SystemPropertyUtils.resolvePlaceholders(property);
+ this.logger.fine("Property '" + propertyKey + "' from environment: " + value);
+ return value;
}
- if (this.properties.containsKey(MAIN)) {
- String mainClass = SystemPropertyUtils.resolvePlaceholders(this.properties
- .getProperty(MAIN));
- this.logger.info("Main class from properties: " + mainClass);
- return mainClass;
+ if (this.properties.containsKey(propertyKey)) {
+ String value = SystemPropertyUtils.resolvePlaceholders(this.properties
+ .getProperty(propertyKey));
+ this.logger.fine("Property '" + propertyKey + "' from properties: " + value);
+ return value;
}
try {
// Prefer home dir for MANIFEST if there is one
- String mainClass = new ExplodedArchive(this.home).getMainClass();
- this.logger.info("Main class from home directory manifest: " + mainClass);
- return mainClass;
+ Manifest manifest = new ExplodedArchive(this.home).getManifest();
+ if (manifest != null) {
+ String value = manifest.getMainAttributes().getValue(manifestKey);
+ this.logger.fine("Property '" + manifestKey
+ + "' from home directory manifest: " + value);
+ return value;
+ }
}
catch (IllegalStateException ex) {
- // Otherwise try the parent archive
- String mainClass = createArchive().getMainClass();
- this.logger.info("Main class from archive manifest: " + mainClass);
- return mainClass;
}
+ // Otherwise try the parent archive
+ Manifest manifest = createArchive().getManifest();
+ if (manifest != null) {
+ String value = manifest.getMainAttributes().getValue(manifestKey);
+ if (value != null) {
+ this.logger.fine("Property '" + manifestKey + "' from archive manifest: "
+ + value);
+ return value;
+ }
+ }
+ return null;
}
@Override
@@ -444,6 +521,28 @@ public class PropertiesLauncher extends Launcher {
new PropertiesLauncher().launch(args);
}
+ public static String toCamelCase(CharSequence string) {
+ if (string == null) {
+ return null;
+ }
+ StringBuilder builder = new StringBuilder();
+ Matcher matcher = WORD_SEPARATOR.matcher(string);
+ int pos = 0;
+ while (matcher.find()) {
+ builder.append(capitalize(string.subSequence(pos, matcher.end()).toString()));
+ pos = matcher.end();
+ }
+ builder.append(capitalize(string.subSequence(pos, string.length()).toString()));
+ return builder.toString();
+ }
+
+ private static Object capitalize(String str) {
+ StringBuilder sb = new StringBuilder(str.length());
+ sb.append(Character.toUpperCase(str.charAt(0)));
+ sb.append(str.substring(1));
+ return sb.toString();
+ }
+
/**
* Convenience class for finding nested archives (archive entries that can be
* classpath entries).
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 11228f0f8f3..b0b5c2411f3 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
@@ -18,14 +18,19 @@ package org.springframework.boot.loader;
import java.io.File;
import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.springframework.boot.loader.archive.Archive;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
@@ -98,6 +103,27 @@ public class PropertiesLauncherTests {
waitFor("Hello World");
}
+ @Test
+ public void testUserSpecifiedClassLoader() throws Exception {
+ System.setProperty("loader.path", "jars/app.jar");
+ System.setProperty("loader.classLoader", URLClassLoader.class.getName());
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ assertEquals("[jars/app.jar]", ReflectionTestUtils.getField(launcher, "paths")
+ .toString());
+ launcher.launch(new String[0]);
+ waitFor("Hello World");
+ }
+
+ @Test
+ public void testCustomClassLoaderCreation() throws Exception {
+ System.setProperty("loader.classLoader", TestLoader.class.getName());
+ PropertiesLauncher launcher = new PropertiesLauncher();
+ ClassLoader loader = launcher
+ .createClassLoader(Collections. emptyList());
+ assertNotNull(loader);
+ assertEquals(TestLoader.class.getName(), loader.getClass().getName());
+ }
+
@Test
public void testUserSpecifiedConfigPathWins() throws Exception {
@@ -132,4 +158,16 @@ public class PropertiesLauncherTests {
assertTrue("Timed out waiting for (" + value + ")", timeout);
}
+ public static class TestLoader extends URLClassLoader {
+
+ public TestLoader(ClassLoader parent) {
+ super(new URL[0], parent);
+ }
+
+ @Override
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ return super.findClass(name);
+ }
+ }
+
}