Re-organize the Launcher code a bit

pull/59/merge
Dave Syer 11 years ago
parent e9fd7c96b8
commit a70d293c87

@ -0,0 +1,104 @@
/*
* 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.net.URI;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* Base class for launchers that can start an application with a fully configured
* classpath.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class AbstractLauncher implements ArchiveFilter {
private Logger logger = Logger.getLogger(AbstractLauncher.class.getName());
private LaunchHelper helper = new LaunchHelper();
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
* @param args the incoming arguments
*/
public void launch(String[] args) {
try {
launch(args, getClass().getProtectionDomain());
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
/**
* Launch the application given the protection domain.
* @param args the incoming arguments
* @param protectionDomain the protection domain
* @throws Exception
*/
protected void launch(String[] args, ProtectionDomain protectionDomain)
throws Exception {
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
String path = (location == null ? null : location.getPath());
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException(
"Unable to determine code source archive from " + root);
}
Archive archive = (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
launch(args, archive);
}
/**
* Launch the application given the archive file
* @param args the incoming arguments
* @param archive the underlying (zip/war/jar) archive
* @throws Exception
*/
protected void launch(String[] args, Archive archive) throws Exception {
List<Archive> lib = new ArrayList<Archive>();
lib.addAll(this.helper.findNestedArchives(archive, this));
this.logger.fine("Added " + lib.size() + " entries");
postProcessLib(archive, lib);
String mainClass = this.helper.getMainClass(archive);
this.helper.launch(args, mainClass, lib);
}
/**
* Called to post-process lib entries before they are used. Implementations can add
* and remove entries.
* @param archive the archive
* @param lib the existing lib
* @throws Exception
*/
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
}
}

@ -22,7 +22,7 @@ import java.net.URL;
import java.util.jar.Manifest;
/**
* An archive that can be launched by the {@link Launcher}.
* An archive that can be launched by the {@link AbstractLauncher}.
*
* @author Phillip Webb
* @see JarFileArchive

@ -0,0 +1,26 @@
/*
* 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;
/**
* @author Dave Syer
*/
public interface ArchiveFilter {
public boolean isArchive(Archive.Entry entry);
}

@ -19,15 +19,19 @@ package org.springframework.boot.loader;
import java.util.List;
/**
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* included inside a {@code /lib} directory.
* {@link AbstractLauncher} for JAR based archives. This launcher assumes that dependency
* jars are included inside a {@code /lib} directory.
*
* @author Phillip Webb
*/
public class JarLauncher extends Launcher {
public class JarLauncher extends AbstractLauncher {
public static void main(String[] args) {
new JarLauncher().launch(args);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
public boolean isArchive(Archive.Entry entry) {
return !entry.isDirectory() && entry.getName().startsWith("lib/");
}
@ -36,8 +40,4 @@ public class JarLauncher extends Launcher {
lib.add(0, archive);
}
public static void main(String[] args) {
new JarLauncher().launch(args);
}
}

@ -1,5 +1,5 @@
/*
* Copyright 2013 the original author or authors.
* 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.
@ -16,110 +16,71 @@
package org.springframework.boot.loader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URI;
import java.net.URL;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.logging.Logger;
/**
* Base class for launchers that can start an application with a fully configured
* classpath.
* Common convenience methods shared by launcher implementations.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class Launcher {
public class LaunchHelper {
private Logger logger = Logger.getLogger(Launcher.class.getName());
private Logger logger = Logger.getLogger(LaunchHelper.class.getName());
/**
* The main runner class. This must be loaded by the created ClassLoader so cannot be
* directly referenced.
*/
private static final String RUNNER_CLASS = Launcher.class.getPackage().getName()
+ ".MainMethodRunner";
private static final String RUNNER_CLASS = AbstractLauncher.class.getPackage()
.getName() + ".MainMethodRunner";
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
* @param args the incoming arguments
*/
public void launch(String[] args) {
try {
launch(args, getClass().getProtectionDomain());
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
/**
* Launch the application given the protection domain.
* @param args the incoming arguments
* @param protectionDomain the protection domain
* @param mainClass the main class
* @param lib a collection of archives (zip/jar/war or directory)
* @throws Exception
*/
protected void launch(String[] args, ProtectionDomain protectionDomain)
public void launch(String[] args, String mainClass, List<Archive> lib)
throws Exception {
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
String path = (location == null ? null : location.getPath());
if (path == null) {
throw new IllegalStateException("Unable to determine code source archive");
}
File root = new File(path);
if (!root.exists()) {
throw new IllegalStateException(
"Unable to determine code source archive from " + root);
}
Archive archive = (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
launch(args, archive);
ClassLoader classLoader = createClassLoader(lib);
launch(args, mainClass, classLoader);
}
/**
* Launch the application given the archive file
* @param args the incoming arguments
* @param archive the underlying (zip/war/jar) archive
* @param archive the archive to search
* @return an accumulation of nested archives
* @throws Exception
*/
protected void launch(String[] args, Archive archive) throws Exception {
public List<Archive> findNestedArchives(Archive archive, ArchiveFilter filter)
throws Exception {
List<Archive> lib = new ArrayList<Archive>();
for (Archive.Entry entry : archive.getEntries()) {
if (isNestedArchive(entry)) {
if (filter.isArchive(entry)) {
this.logger.fine("Adding: " + entry.getName());
lib.add(archive.getNestedArchive(entry));
}
}
this.logger.fine("Added " + lib.size() + " entries");
postProcessLib(archive, lib);
ClassLoader classLoader = createClassLoader(lib);
launch(args, archive, classLoader);
return lib;
}
/**
* Determine if the specified {@link JarEntry} is a nested item that should be added
* to the classpath. The method is called once for each entry.
* @param jarEntry the jar entry
* @return {@code true} if the entry is a nested item (jar or folder)
*/
protected abstract boolean isNestedArchive(Archive.Entry jarEntry);
/**
* Called to post-process lib entries before they are used. Implementations can add
* and remove entries.
* Obtain the main class that should be used to launch the application. By default
* this method uses a {@code Start-Class} manifest entry.
* @param archive the archive
* @param lib the existing lib
* @return the main class
* @throws Exception
*/
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
public String getMainClass(Archive archive) throws Exception {
String mainClass = archive.getManifest().getMainAttributes()
.getValue("Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
}
return mainClass;
}
/**
@ -136,26 +97,15 @@ public abstract class Launcher {
return createClassLoader(urls);
}
/**
* Create a classloader for the specified URLs
* @param urls the URLs
* @return the classloader
* @throws Exception
*/
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
/**
* Launch the application given the archive file and a fully configured classloader.
* @param args the incoming arguments
* @param archive the archive
* @param mainClass the main class to run
* @param classLoader the classloader
* @throws Exception
*/
protected void launch(String[] args, Archive archive, ClassLoader classLoader)
protected void launch(String[] args, String mainClass, ClassLoader classLoader)
throws Exception {
String mainClass = getMainClass(archive);
Runnable runner = createMainMethodRunner(mainClass, args, classLoader);
Thread runnerThread = new Thread(runner);
runnerThread.setContextClassLoader(classLoader);
@ -164,19 +114,13 @@ public abstract class Launcher {
}
/**
* Obtain the main class that should be used to launch the application. By default
* this method uses a {@code Start-Class} manifest entry.
* @param archive the archive
* @return the main class
* Create a classloader for the specified URLs
* @param urls the URLs
* @return the classloader
* @throws Exception
*/
protected String getMainClass(Archive archive) throws Exception {
String mainClass = archive.getManifest().getMainAttributes()
.getValue("Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
}
return mainClass;
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
/**

@ -25,7 +25,7 @@ import java.security.PrivilegedExceptionAction;
import org.springframework.boot.loader.jar.RandomAccessJarFile;
/**
* {@link ClassLoader} used by the {@link Launcher}.
* {@link ClassLoader} used by the {@link AbstractLauncher}.
*
* @author Phillip Webb
*/

@ -19,7 +19,7 @@ package org.springframework.boot.loader;
import java.lang.reflect.Method;
/**
* Utility class that used by {@link Launcher}s to call a main method. This class allows
* Utility class that used by {@link AbstractLauncher}s to call a main method. This class allows
* methods to be executed within a thread configured with a specific context classloader.
*
* @author Phillip Webb

@ -23,7 +23,6 @@ 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.List;
@ -33,8 +32,8 @@ import java.util.logging.Logger;
import org.springframework.boot.loader.util.SystemPropertyUtils;
/**
* {@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
* {@link AbstractLauncher} 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.
*
* <p>
@ -49,19 +48,20 @@ import org.springframework.boot.loader.util.SystemPropertyUtils;
* System properties in case the file doesn't exist):
*
* <ul>
* <li><code>loader.path</code>: a comma-separated list of classpath directories
* (containing file resources and/or archives in *.jar or *.zip). Defaults to
* <code>lib</code> (i.e. a directory in the current working directory)</li>
* <li><code>loader.path</code>: a comma-separated list of directories to append to the
* classpath (containing file resources and/or nested archives in *.jar or *.zip).
* Defaults to <code>lib</code> (i.e. a directory in the current working directory)</li>
* <li><code>loader.main</code>: the main method to delegate execution to once the class
* loader is set up. No default, but will fall back to looking in a
* <code>MANIFEST.MF</code> if there is one.</li>
* loader is set up. No default, but will fall back to looking for a
* <code>Start-Class</code> in a <code>MANIFEST.MF</code>, if there is one in
* <code>${loader.home}/META-INF</code>.</li>
* </ul>
*
* @author Dave Syer
*/
public class PropertiesLauncher extends Launcher {
public class PropertiesLauncher implements ArchiveFilter {
private Logger logger = Logger.getLogger(Launcher.class.getName());
private Logger logger = Logger.getLogger(AbstractLauncher.class.getName());
/**
* Properties key for main class
@ -85,10 +85,32 @@ public class PropertiesLauncher extends Launcher {
private Properties properties = new Properties();
private LaunchHelper helper = new LaunchHelper();
public static void main(String[] args) {
new PropertiesLauncher().launch(args);
}
/**
* Launch the application. This method is the initial entry point that should be
* called by a subclass {@code public static void main(String[] args)} method.
* @param args the incoming arguments
*/
public void launch(String[] args) {
try {
File home = getHomeDirectory();
initialize(home);
this.helper.launch(args, getMainClass(home), getLibrary(home, this.paths));
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
}
@Override
protected void launch(String[] args, ProtectionDomain protectionDomain)
throws Exception {
launch(args, new ExplodedArchive(getHomeDirectory()));
public boolean isArchive(Archive.Entry entry) {
return entry.isDirectory() || isArchive(entry.getName());
}
protected File getHomeDirectory() {
@ -96,28 +118,62 @@ public class PropertiesLauncher extends Launcher {
"${user.dir}")));
}
/**
* Look in various places for a properties file to extract loader settings. Default to
* <code>application.properties</code> 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 String getMainClass(File home) throws Exception {
if (System.getProperty(MAIN) != null) {
return SystemPropertyUtils.resolvePlaceholders(System.getProperty(MAIN));
}
if (this.properties.containsKey(MAIN)) {
return SystemPropertyUtils.resolvePlaceholders(this.properties
.getProperty(MAIN));
}
return this.helper.getMainClass(new ExplodedArchive(home));
}
protected void initialize() throws Exception {
initializeProperties();
protected void initialize(File home) throws Exception {
initializeProperties(home);
initializePaths();
}
private void initializeProperties() throws Exception, IOException {
private boolean isArchive(String name) {
return name.endsWith(".jar") || name.endsWith(".zip");
}
/**
* Search the configured paths and look for nested archives.
*
* @param home the home directory for this launch
* @param paths the directory roots for classpath entries
* @return a library of archives that can be used as a classpath
* @throws Exception
*/
private List<Archive> getLibrary(File home, List<String> paths) throws Exception {
List<Archive> lib = new ArrayList<Archive>();
for (String path : paths) {
String root = cleanupPath(stripFileUrlPrefix(path));
File file = new File(root);
if (!root.startsWith("/")) {
file = new File(home, root);
}
if (file.isDirectory()) {
this.logger.info("Adding classpath entries from " + path);
Archive archive = new ExplodedArchive(file);
lib.addAll(this.helper.findNestedArchives(archive, this));
lib.add(0, archive);
}
else {
this.logger.info("No directory found at " + path);
}
}
return lib;
}
private void initializeProperties(File home) throws Exception, IOException {
String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_NAME, "application")) + ".properties";
InputStream resource = getClasspathResource(config);
if (resource == null) {
resource = getResource(new File(home, config).getAbsolutePath());
}
if (resource == null) {
config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_LOCATION, config));
@ -262,43 +318,4 @@ public class PropertiesLauncher extends Launcher {
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<Archive> lib) throws Exception {
lib.add(0, archive);
}
@Override
protected String getMainClass(Archive archive) throws Exception {
if (System.getProperty(MAIN) != null) {
return SystemPropertyUtils.resolvePlaceholders(System.getProperty(MAIN));
}
if (this.properties.containsKey(MAIN)) {
return SystemPropertyUtils.resolvePlaceholders(this.properties
.getProperty(MAIN));
}
return super.getMainClass(archive);
}
public static void main(String[] args) {
new PropertiesLauncher().launch(args);
}
}

@ -20,16 +20,20 @@ import java.io.IOException;
import java.util.List;
/**
* {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
* Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided},
* classes are loaded from {@code WEB-INF/classes}.
* {@link AbstractLauncher} for WAR based archives. This launcher for standard WAR
* archives. Supports dependencies in {@code WEB-INF/lib} as well as
* {@code WEB-INF/lib-provided}, classes are loaded from {@code WEB-INF/classes}.
*
* @author Phillip Webb
*/
public class WarLauncher extends Launcher {
public class WarLauncher extends AbstractLauncher {
public static void main(String[] args) {
new WarLauncher().launch(args);
}
@Override
protected boolean isNestedArchive(Archive.Entry entry) {
public boolean isArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals("WEB-INF/classes/");
}
@ -64,8 +68,4 @@ public class WarLauncher extends Launcher {
});
}
public static void main(String[] args) {
new WarLauncher().launch(args);
}
}

@ -48,14 +48,14 @@ public class PropertiesLauncherTests {
@Test
public void testUserSpecifiedMain() throws Exception {
this.launcher.initialize();
this.launcher.initialize(new File("."));
assertEquals("demo.Application", this.launcher.getMainClass(null));
}
@Test
public void testUserSpecifiedConfigName() throws Exception {
System.setProperty("loader.config.name", "foo");
this.launcher.initialize();
this.launcher.initialize(new File("."));
assertEquals("my.Application", this.launcher.getMainClass(null));
assertEquals("[etc/]", ReflectionTestUtils.getField(this.launcher, "paths")
.toString());
@ -64,7 +64,7 @@ public class PropertiesLauncherTests {
@Test
public void testSystemPropertySpecifiedMain() throws Exception {
System.setProperty("loader.main", "foo.Bar");
this.launcher.initialize();
this.launcher.initialize(new File("."));
assertEquals("foo.Bar", this.launcher.getMainClass(null));
}

Loading…
Cancel
Save