From 033250195bd01bfdbce4257ed1de253f7a11d937 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 31 Dec 2013 14:35:51 +0000 Subject: [PATCH] Allow user to specify classLoader as loader property PropertiesLauncher now supports creating its own class loader from looader.classLoader property. It will succeed if the implementation specified has a default constructor or one that takes a parent class loader, or one that takes a URL[] and a parent class loader (like URLClassLoader). --- spring-boot-cli/pom.xml | 5 +- .../boot/loader/PropertiesLauncher.java | 131 +++++++++++++++--- .../boot/loader/PropertiesLauncherTests.java | 38 +++++ 3 files changed, 157 insertions(+), 17 deletions(-) diff --git a/spring-boot-cli/pom.xml b/spring-boot-cli/pom.xml index d03f1885ef..33cf8056f8 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 6cf127f639..ab490ad830 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 11228f0f8f..b0b5c2411f 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); + } + } + }