Commit 03325019 authored by Dave Syer's avatar Dave Syer

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).
parent f5f41fef
...@@ -233,11 +233,14 @@ ...@@ -233,11 +233,14 @@
<archive> <archive>
<manifest> <manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries> <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<mainClass>org.springframework.boot.loader.JarLauncher</mainClass> <mainClass>org.springframework.boot.loader.PropertiesLauncher</mainClass>
</manifest> </manifest>
<manifestEntries> <manifestEntries>
<Start-Class>${start-class}</Start-Class> <Start-Class>${start-class}</Start-Class>
</manifestEntries> </manifestEntries>
<manifestEntries>
<Class-Loader>groovy.lang.GroovyClassLoader</Class-Loader>
</manifestEntries>
</archive> </archive>
</configuration> </configuration>
</execution> </execution>
......
...@@ -33,7 +33,11 @@ import java.util.Arrays; ...@@ -33,7 +33,11 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger; 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;
import org.springframework.boot.loader.archive.Archive.Entry; import org.springframework.boot.loader.archive.Archive.Entry;
...@@ -116,6 +120,8 @@ public class PropertiesLauncher extends Launcher { ...@@ -116,6 +120,8 @@ public class PropertiesLauncher extends Launcher {
private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/"); private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/");
private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+");
private final File home; private final File home;
private List<String> paths = new ArrayList<String>(DEFAULT_PATHS); private List<String> paths = new ArrayList<String>(DEFAULT_PATHS);
...@@ -123,6 +129,9 @@ public class PropertiesLauncher extends Launcher { ...@@ -123,6 +129,9 @@ public class PropertiesLauncher extends Launcher {
private Properties properties = new Properties(); private Properties properties = new Properties();
public PropertiesLauncher() { public PropertiesLauncher() {
if (!isDebug()) {
this.logger.setLevel(Level.SEVERE);
}
try { try {
this.home = getHomeDirectory(); this.home = getHomeDirectory();
initializeProperties(this.home); initializeProperties(this.home);
...@@ -133,6 +142,22 @@ public class PropertiesLauncher extends Launcher { ...@@ -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() { protected File getHomeDirectory() {
return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME, return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
"${user.dir}"))); "${user.dir}")));
...@@ -290,30 +315,82 @@ public class PropertiesLauncher extends Launcher { ...@@ -290,30 +315,82 @@ public class PropertiesLauncher extends Launcher {
@Override @Override
protected String getMainClass() throws Exception { 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<Archive> 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) { if (property != null) {
String mainClass = SystemPropertyUtils.resolvePlaceholders(property); String value = SystemPropertyUtils.resolvePlaceholders(property);
this.logger.info("Main class from environment: " + mainClass); this.logger.fine("Property '" + propertyKey + "' from environment: " + value);
return mainClass; return value;
} }
if (this.properties.containsKey(MAIN)) { if (this.properties.containsKey(propertyKey)) {
String mainClass = SystemPropertyUtils.resolvePlaceholders(this.properties String value = SystemPropertyUtils.resolvePlaceholders(this.properties
.getProperty(MAIN)); .getProperty(propertyKey));
this.logger.info("Main class from properties: " + mainClass); this.logger.fine("Property '" + propertyKey + "' from properties: " + value);
return mainClass; return value;
} }
try { try {
// Prefer home dir for MANIFEST if there is one // Prefer home dir for MANIFEST if there is one
String mainClass = new ExplodedArchive(this.home).getMainClass(); Manifest manifest = new ExplodedArchive(this.home).getManifest();
this.logger.info("Main class from home directory manifest: " + mainClass); if (manifest != null) {
return mainClass; String value = manifest.getMainAttributes().getValue(manifestKey);
this.logger.fine("Property '" + manifestKey
+ "' from home directory manifest: " + value);
return value;
}
} }
catch (IllegalStateException ex) { 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 @Override
...@@ -444,6 +521,28 @@ public class PropertiesLauncher extends Launcher { ...@@ -444,6 +521,28 @@ public class PropertiesLauncher extends Launcher {
new PropertiesLauncher().launch(args); 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 * Convenience class for finding nested archives (archive entries that can be
* classpath entries). * classpath entries).
......
...@@ -18,14 +18,19 @@ package org.springframework.boot.loader; ...@@ -18,14 +18,19 @@ package org.springframework.boot.loader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
...@@ -98,6 +103,27 @@ public class PropertiesLauncherTests { ...@@ -98,6 +103,27 @@ public class PropertiesLauncherTests {
waitFor("Hello World"); 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.<Archive> emptyList());
assertNotNull(loader);
assertEquals(TestLoader.class.getName(), loader.getClass().getName());
}
@Test @Test
public void testUserSpecifiedConfigPathWins() throws Exception { public void testUserSpecifiedConfigPathWins() throws Exception {
...@@ -132,4 +158,16 @@ public class PropertiesLauncherTests { ...@@ -132,4 +158,16 @@ public class PropertiesLauncherTests {
assertTrue("Timed out waiting for (" + value + ")", timeout); 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);
}
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment