Commit 15bc25dc authored by Phillip Webb's avatar Phillip Webb

Further re-organization of launcher code

parent 0e0eb7d3
...@@ -13,14 +13,15 @@ ...@@ -13,14 +13,15 @@
<main.basedir>${basedir}/../..</main.basedir> <main.basedir>${basedir}/../..</main.basedir>
</properties> </properties>
<dependencies> <dependencies>
<!-- TODO: maybe put these in the parent? -->
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId> <artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,44 +22,34 @@ import java.security.CodeSource; ...@@ -22,44 +22,34 @@ import java.security.CodeSource;
import java.security.ProtectionDomain; import java.security.ProtectionDomain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.jar.JarEntry;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.boot.loader.archive.Archive.EntryFilter;
import org.springframework.boot.loader.archive.ExplodedArchive;
import org.springframework.boot.loader.archive.JarFileArchive;
/** /**
* Base class for launchers that can start an application with a fully configured * Base class for executable archive {@link Launcher}s.
* classpath.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer
*/ */
public abstract class AbstractLauncher implements ArchiveFilter { public abstract class ExecutableArchiveLauncher extends Launcher {
private Logger logger = Logger.getLogger(AbstractLauncher.class.getName());
private LaunchHelper helper = new LaunchHelper(); private final Archive archive;
/** public ExecutableArchiveLauncher() {
* 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 { try {
launch(args, getClass().getProtectionDomain()); this.archive = createArchive();
} }
catch (Exception ex) { catch (Exception ex) {
ex.printStackTrace(); throw new IllegalStateException(ex);
System.exit(1);
} }
} }
/** private Archive createArchive() throws Exception {
* Launch the application given the protection domain. ProtectionDomain protectionDomain = getClass().getProtectionDomain();
* @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(); CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource == null ? null : codeSource.getLocation().toURI()); URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
String path = (location == null ? null : location.getPath()); String path = (location == null ? null : location.getPath());
...@@ -71,34 +61,46 @@ public abstract class AbstractLauncher implements ArchiveFilter { ...@@ -71,34 +61,46 @@ public abstract class AbstractLauncher implements ArchiveFilter {
throw new IllegalStateException( throw new IllegalStateException(
"Unable to determine code source archive from " + root); "Unable to determine code source archive from " + root);
} }
Archive archive = (root.isDirectory() ? new ExplodedArchive(root) return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
: new JarFileArchive(root)); }
launch(args, archive);
protected final Archive getArchive() {
return this.archive;
}
@Override
protected String getMainClass() throws Exception {
return this.archive.getMainClass();
}
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> archives = new ArrayList<Archive>(
this.archive.getNestedArchives(new EntryFilter() {
@Override
public boolean matches(Entry entry) {
return isNestedArchive(entry);
}
}));
postProcessClassPathArchives(archives);
return archives;
} }
/** /**
* Launch the application given the archive file * Determine if the specified {@link JarEntry} is a nested item that should be added
* @param args the incoming arguments * to the classpath. The method is called once for each entry.
* @param archive the underlying (zip/war/jar) archive * @param entry the jar entry
* @throws Exception * @return {@code true} if the entry is a nested item (jar or folder)
*/ */
protected void launch(String[] args, Archive archive) throws Exception { protected abstract boolean isNestedArchive(Archive.Entry entry);
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 * Called to post-process archive entries before they are used. Implementations can
* and remove entries. * add and remove entries.
* @param archive the archive * @param archives the archives
* @param lib the existing lib
* @throws Exception * @throws Exception
*/ */
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception { protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
} }
} }
...@@ -18,26 +18,27 @@ package org.springframework.boot.loader; ...@@ -18,26 +18,27 @@ package org.springframework.boot.loader;
import java.util.List; import java.util.List;
import org.springframework.boot.loader.archive.Archive;
/** /**
* {@link AbstractLauncher} for JAR based archives. This launcher assumes that dependency * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* jars are included inside a {@code /lib} directory. * included inside a {@code /lib} directory.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class JarLauncher extends AbstractLauncher { public class JarLauncher extends ExecutableArchiveLauncher {
public static void main(String[] args) {
new JarLauncher().launch(args);
}
@Override @Override
public boolean isArchive(Archive.Entry entry) { protected boolean isNestedArchive(Archive.Entry entry) {
return !entry.isDirectory() && entry.getName().startsWith("lib/"); return !entry.isDirectory() && entry.getName().startsWith("lib/");
} }
@Override @Override
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception { protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
lib.add(0, archive); archives.add(0, getArchive());
} }
public static void main(String[] args) {
new JarLauncher().launch(args);
}
} }
...@@ -25,7 +25,7 @@ import java.security.PrivilegedExceptionAction; ...@@ -25,7 +25,7 @@ import java.security.PrivilegedExceptionAction;
import org.springframework.boot.loader.jar.RandomAccessJarFile; import org.springframework.boot.loader.jar.RandomAccessJarFile;
/** /**
* {@link ClassLoader} used by the {@link AbstractLauncher}. * {@link ClassLoader} used by the {@link Launcher}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
......
/* /*
* Copyright 2012-2013 the original author or authors. * Copyright 2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -22,79 +22,64 @@ import java.util.ArrayList; ...@@ -22,79 +22,64 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.springframework.boot.loader.archive.Archive;
/** /**
* Common convenience methods shared by launcher implementations. * Base class for launchers that can start an application with a fully configured
* classpath backed by one or more {@link Archive}s.
* *
* @author Phillip Webb
* @author Dave Syer * @author Dave Syer
*/ */
public class LaunchHelper { public abstract class Launcher {
private Logger logger = Logger.getLogger(LaunchHelper.class.getName()); protected Logger logger = Logger.getLogger(Launcher.class.getName());
/** /**
* The main runner class. This must be loaded by the created ClassLoader so cannot be * The main runner class. This must be loaded by the created ClassLoader so cannot be
* directly referenced. * directly referenced.
*/ */
private static final String RUNNER_CLASS = AbstractLauncher.class.getPackage() private static final String RUNNER_CLASS = Launcher.class.getPackage().getName()
.getName() + ".MainMethodRunner"; + ".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 * @param args the incoming arguments
* @param mainClass the main class
* @param lib a collection of archives (zip/jar/war or directory)
* @throws Exception
*/ */
public void launch(String[] args, String mainClass, List<Archive> lib) protected void launch(String[] args) {
throws Exception { try {
ClassLoader classLoader = createClassLoader(lib); ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, mainClass, classLoader); launch(args, getMainClass(), classLoader);
} }
catch (Exception ex) {
/** ex.printStackTrace();
* @param archive the archive to search System.exit(1);
* @return an accumulation of nested archives
* @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 (filter.isArchive(entry)) {
this.logger.fine("Adding: " + entry.getName());
lib.add(archive.getNestedArchive(entry));
} }
} }
return lib;
}
/** /**
* Obtain the main class that should be used to launch the application. By default * Create a classloader for the specified archives.
* this method uses a {@code Start-Class} manifest entry. * @param archives the archives
* @param archive the archive * @return the classloader
* @return the main class
* @throws Exception * @throws Exception
*/ */
public String getMainClass(Archive archive) throws Exception { protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
String mainClass = archive.getManifest().getMainAttributes() List<URL> urls = new ArrayList<URL>(archives.size());
.getValue("Start-Class"); for (Archive archive : archives) {
if (mainClass == null) { urls.add(archive.getUrl());
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
} }
return mainClass; return createClassLoader(urls.toArray(new URL[urls.size()]));
} }
/** /**
* Create a classloader for the specified lib. * Create a classloader for the specified URLs
* @param lib the lib * @param urls the URLs
* @return the classloader * @return the classloader
* @throws Exception * @throws Exception
*/ */
protected ClassLoader createClassLoader(List<Archive> lib) throws Exception { protected ClassLoader createClassLoader(URL[] urls) throws Exception {
URL[] urls = new URL[lib.size()]; return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
for (int i = 0; i < urls.length; i++) {
urls[i] = lib.get(i).getUrl();
}
return createClassLoader(urls);
} }
/** /**
...@@ -113,16 +98,6 @@ public class LaunchHelper { ...@@ -113,16 +98,6 @@ public class LaunchHelper {
runnerThread.start(); runnerThread.start();
} }
/**
* 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());
}
/** /**
* Create the {@code MainMethodRunner} used to launch the application. * Create the {@code MainMethodRunner} used to launch the application.
* @param mainClass the main class * @param mainClass the main class
...@@ -139,4 +114,17 @@ public class LaunchHelper { ...@@ -139,4 +114,17 @@ public class LaunchHelper {
return (Runnable) constructor.newInstance(mainClass, args); return (Runnable) constructor.newInstance(mainClass, args);
} }
/**
* Returns the main class that should be launched.
* @return the name of the main class
* @throws Exception
*/
protected abstract String getMainClass() throws Exception;
/**
* Returns the archives that will be used to construct the class path.
* @return the class path archives
* @throws Exception
*/
protected abstract List<Archive> getClassPathArchives() throws Exception;
} }
...@@ -19,8 +19,8 @@ package org.springframework.boot.loader; ...@@ -19,8 +19,8 @@ package org.springframework.boot.loader;
import java.lang.reflect.Method; import java.lang.reflect.Method;
/** /**
* Utility class that used by {@link AbstractLauncher}s to call a main method. This class allows * Utility class that used by {@link Launcher}s to call a main method. This class allows
* methods to be executed within a thread configured with a specific context classloader. * methods to be executed within a thread configured with a specific context class loader.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
......
...@@ -30,11 +30,15 @@ import java.util.List; ...@@ -30,11 +30,15 @@ import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.boot.loader.archive.Archive.EntryFilter;
import org.springframework.boot.loader.archive.ExplodedArchive;
import org.springframework.boot.loader.util.SystemPropertyUtils; import org.springframework.boot.loader.util.SystemPropertyUtils;
/** /**
* {@link AbstractLauncher} for archives with user-configured classpath and main class via * {@link Launcher} for archives with user-configured classpath and main class via a
* a properties file. This model is often more flexible and more amenable to creating * 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. * well-behaved OS-level services than a model based on executable jars.
* *
* <p> * <p>
...@@ -60,9 +64,9 @@ import org.springframework.boot.loader.util.SystemPropertyUtils; ...@@ -60,9 +64,9 @@ import org.springframework.boot.loader.util.SystemPropertyUtils;
* *
* @author Dave Syer * @author Dave Syer
*/ */
public class PropertiesLauncher implements ArchiveFilter { public class PropertiesLauncher extends Launcher {
private Logger logger = Logger.getLogger(AbstractLauncher.class.getName()); private Logger logger = Logger.getLogger(Launcher.class.getName());
/** /**
* Properties key for main class * Properties key for main class
...@@ -105,36 +109,21 @@ public class PropertiesLauncher implements ArchiveFilter { ...@@ -105,36 +109,21 @@ public class PropertiesLauncher implements ArchiveFilter {
private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/"); private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/");
private final File home;
private List<String> paths = new ArrayList<String>(DEFAULT_PATHS); private List<String> paths = new ArrayList<String>(DEFAULT_PATHS);
private Properties properties = new Properties(); private Properties properties = new Properties();
private LaunchHelper helper = new LaunchHelper(); public PropertiesLauncher() {
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 { try {
File home = getHomeDirectory(); this.home = getHomeDirectory();
initialize(home); initializeProperties(this.home);
this.helper.launch(args, getMainClass(home), getLibrary(home, this.paths)); initializePaths();
} }
catch (Exception ex) { catch (Exception ex) {
ex.printStackTrace(); throw new IllegalStateException(ex);
System.exit(1);
}
} }
@Override
public boolean isArchive(Archive.Entry entry) {
return entry.isDirectory() || isArchive(entry.getName());
} }
protected File getHomeDirectory() { protected File getHomeDirectory() {
...@@ -142,55 +131,6 @@ public class PropertiesLauncher implements ArchiveFilter { ...@@ -142,55 +131,6 @@ public class PropertiesLauncher implements ArchiveFilter {
"${user.dir}"))); "${user.dir}")));
} }
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(File home) throws Exception {
initializeProperties(home);
initializePaths();
}
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 { private void initializeProperties(File home) throws Exception, IOException {
String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty( String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_NAME, "application")) + ".properties"; CONFIG_NAME, "application")) + ".properties";
...@@ -346,6 +286,46 @@ public class PropertiesLauncher implements ArchiveFilter { ...@@ -346,6 +286,46 @@ public class PropertiesLauncher implements ArchiveFilter {
return paths; return paths;
} }
@Override
protected String getMainClass() 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 new ExplodedArchive(this.home).getMainClass();
}
@Override
protected List<Archive> getClassPathArchives() throws Exception {
List<Archive> lib = new ArrayList<Archive>();
for (String path : this.paths) {
String root = cleanupPath(stripFileUrlPrefix(path));
File file = new File(root);
if (!root.startsWith("/")) {
file = new File(this.home, root);
}
if (file.isDirectory()) {
this.logger.info("Adding classpath entries from " + path);
Archive archive = new ExplodedArchive(file);
lib.addAll(archive.getNestedArchives(new EntryFilter() {
@Override
public boolean matches(Entry entry) {
return entry.isDirectory() || entry.getName().endsWith(".jar")
|| entry.getName().endsWith(".zip");
}
}));
lib.add(0, archive);
}
else {
this.logger.info("No directory found at " + path);
}
}
return lib;
}
private String cleanupPath(String path) { private String cleanupPath(String path) {
path = path.trim(); path = path.trim();
// Always a directory // Always a directory
...@@ -359,4 +339,8 @@ public class PropertiesLauncher implements ArchiveFilter { ...@@ -359,4 +339,8 @@ public class PropertiesLauncher implements ArchiveFilter {
return path; return path;
} }
public static void main(String[] args) {
new PropertiesLauncher().launch(args);
}
} }
...@@ -19,21 +19,19 @@ package org.springframework.boot.loader; ...@@ -19,21 +19,19 @@ package org.springframework.boot.loader;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import org.springframework.boot.loader.archive.Archive;
/** /**
* {@link AbstractLauncher} for WAR based archives. This launcher for standard WAR * {@link Launcher} for WAR based archives. This launcher for standard WAR archives.
* archives. Supports dependencies in {@code WEB-INF/lib} as well as * Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided},
* {@code WEB-INF/lib-provided}, classes are loaded from {@code WEB-INF/classes}. * classes are loaded from {@code WEB-INF/classes}.
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class WarLauncher extends AbstractLauncher { public class WarLauncher extends ExecutableArchiveLauncher {
public static void main(String[] args) {
new WarLauncher().launch(args);
}
@Override @Override
public boolean isArchive(Archive.Entry entry) { public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) { if (entry.isDirectory()) {
return entry.getName().equals("WEB-INF/classes/"); return entry.getName().equals("WEB-INF/classes/");
} }
...@@ -44,20 +42,18 @@ public class WarLauncher extends AbstractLauncher { ...@@ -44,20 +42,18 @@ public class WarLauncher extends AbstractLauncher {
} }
@Override @Override
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception { protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
lib.add(0, filterArchive(archive)); archives.add(0, getFilteredArchive());
} }
/** /**
* Filter the specified WAR file to exclude elements that should not appear on the * Filter the specified WAR file to exclude elements that should not appear on the
* classpath. * classpath.
* @param archive the source archive
* @return the filtered archive * @return the filtered archive
* @throws IOException on error * @throws IOException on error
*/ */
protected Archive filterArchive(Archive archive) throws IOException { protected Archive getFilteredArchive() throws IOException {
return archive.getFilteredArchive(new Archive.EntryFilter() { return getArchive().getFilteredArchive(new Archive.EntryRenameFilter() {
@Override @Override
public String apply(String entryName, Archive.Entry entry) { public String apply(String entryName, Archive.Entry entry) {
if (entryName.startsWith("META-INF/") || entryName.startsWith("WEB-INF/")) { if (entryName.startsWith("META-INF/") || entryName.startsWith("WEB-INF/")) {
...@@ -68,4 +64,7 @@ public class WarLauncher extends AbstractLauncher { ...@@ -68,4 +64,7 @@ public class WarLauncher extends AbstractLauncher {
}); });
} }
public static void main(String[] args) {
new WarLauncher().launch(args);
}
} }
...@@ -14,48 +14,67 @@ ...@@ -14,48 +14,67 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader; package org.springframework.boot.loader.archive;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import org.springframework.boot.loader.Launcher;
/** /**
* An archive that can be launched by the {@link AbstractLauncher}. * An archive that can be launched by the {@link Launcher}.
* *
* @author Phillip Webb * @author Phillip Webb
* @see JarFileArchive * @see JarFileArchive
*/ */
public interface Archive { public abstract class Archive {
/**
* Returns a URL that can be used to load the archive.
* @return the archive URL
* @throws MalformedURLException
*/
public abstract URL getUrl() throws MalformedURLException;
/**
* Obtain the main class that should be used to launch the application. By default
* this method uses a {@code Start-Class} manifest entry.
* @return the main class
* @throws Exception
*/
public String getMainClass() throws Exception {
String mainClass = getManifest().getMainAttributes().getValue("Start-Class");
if (mainClass == null) {
throw new IllegalStateException("No 'Start-Class' manifest entry specified");
}
return mainClass;
}
/** /**
* Returns the manifest of the archive. * Returns the manifest of the archive.
* @return the manifest * @return the manifest
* @throws IOException * @throws IOException
*/ */
Manifest getManifest() throws IOException; public abstract Manifest getManifest() throws IOException;
/** /**
* Returns archive entries. * Returns all entries from the archive.
* @return the archive entries * @return the archive entries
*/ */
Iterable<Entry> getEntries(); public abstract Collection<Entry> getEntries();
/**
* Returns a URL that can be used to load the archive.
* @return the archive URL
* @throws MalformedURLException
*/
URL getUrl() throws MalformedURLException;
/** /**
* Returns a nest archive from on the the contained entries. * Returns nested {@link Archive}s for entries that match the specified filter.
* @param entry the entry (may be a directory or file) * @param filter the filter used to limit entries
* @return the nested archive * @return nested archives
* @throws IOException * @throws IOException
*/ */
Archive getNestedArchive(Entry entry) throws IOException; public abstract List<Archive> getNestedArchives(EntryFilter filter)
throws IOException;
/** /**
* Returns a filtered version of the archive. * Returns a filtered version of the archive.
...@@ -63,7 +82,8 @@ public interface Archive { ...@@ -63,7 +82,8 @@ public interface Archive {
* @return a filter archive * @return a filter archive
* @throws IOException * @throws IOException
*/ */
Archive getFilteredArchive(EntryFilter filter) throws IOException; public abstract Archive getFilteredArchive(EntryRenameFilter filter)
throws IOException;
/** /**
* Represents a single entry in the archive. * Represents a single entry in the archive.
...@@ -85,10 +105,24 @@ public interface Archive { ...@@ -85,10 +105,24 @@ public interface Archive {
} }
/** /**
* A filter for archive entries. * Strategy interface to filter {@link Entry Entries}.
*/ */
public static interface EntryFilter { public static interface EntryFilter {
/**
* Apply the jar entry filter.
* @param entry the entry to filter
* @return {@code true} if the filter matches
*/
boolean matches(Entry entry);
}
/**
* Strategy interface to filter or rename {@link Entry Entries}.
*/
public static interface EntryRenameFilter {
/** /**
* Apply the jar entry filter. * Apply the jar entry filter.
* @param entryName the current entry name. This may be different that the * @param entryName the current entry name. This may be different that the
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader; package org.springframework.boot.loader.archive;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
...@@ -24,10 +24,13 @@ import java.net.MalformedURLException; ...@@ -24,10 +24,13 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLStreamHandler; import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.jar.Manifest; import java.util.jar.Manifest;
...@@ -37,7 +40,7 @@ import java.util.jar.Manifest; ...@@ -37,7 +40,7 @@ import java.util.jar.Manifest;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class ExplodedArchive implements Archive { public class ExplodedArchive extends Archive {
private static final Set<String> SKIPPED_NAMES = new HashSet<String>(Arrays.asList( private static final Set<String> SKIPPED_NAMES = new HashSet<String>(Arrays.asList(
".", "..")); ".", ".."));
...@@ -82,6 +85,12 @@ public class ExplodedArchive implements Archive { ...@@ -82,6 +85,12 @@ public class ExplodedArchive implements Archive {
} }
} }
@Override
public URL getUrl() throws MalformedURLException {
FilteredURLStreamHandler handler = new FilteredURLStreamHandler();
return new URL("file", "", -1, this.root.getAbsolutePath() + "/", handler);
}
@Override @Override
public Manifest getManifest() throws IOException { public Manifest getManifest() throws IOException {
if (this.manifest == null && this.entries.containsKey(MANIFEST_ENTRY_NAME)) { if (this.manifest == null && this.entries.containsKey(MANIFEST_ENTRY_NAME)) {
...@@ -98,25 +107,28 @@ public class ExplodedArchive implements Archive { ...@@ -98,25 +107,28 @@ public class ExplodedArchive implements Archive {
} }
@Override @Override
public Iterable<Entry> getEntries() { public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
return this.entries.values(); List<Archive> nestedArchives = new ArrayList<Archive>();
for (Entry entry : getEntries()) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
} }
@Override @Override
public URL getUrl() throws MalformedURLException { public Collection<Entry> getEntries() {
FilteredURLStreamHandler handler = new FilteredURLStreamHandler(); return Collections.unmodifiableCollection(this.entries.values());
return new URL("file", "", -1, this.root.getAbsolutePath() + "/", handler);
// return this.root.toURI().toURL();
} }
@Override protected Archive getNestedArchive(Entry entry) throws IOException {
public Archive getNestedArchive(Entry entry) throws IOException {
File file = ((FileEntry) entry).getFile(); File file = ((FileEntry) entry).getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file)); return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
} }
@Override @Override
public Archive getFilteredArchive(EntryFilter filter) throws IOException { public Archive getFilteredArchive(EntryRenameFilter filter) throws IOException {
Map<String, Entry> filteredEntries = new LinkedHashMap<String, Archive.Entry>(); Map<String, Entry> filteredEntries = new LinkedHashMap<String, Archive.Entry>();
for (Map.Entry<String, Entry> entry : this.entries.entrySet()) { for (Map.Entry<String, Entry> entry : this.entries.entrySet()) {
String filteredName = filter.apply(entry.getKey(), entry.getValue()); String filteredName = filter.apply(entry.getKey(), entry.getValue());
......
...@@ -14,13 +14,14 @@ ...@@ -14,13 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader; package org.springframework.boot.loader.archive;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
...@@ -35,7 +36,7 @@ import org.springframework.boot.loader.jar.RandomAccessJarFile; ...@@ -35,7 +36,7 @@ import org.springframework.boot.loader.jar.RandomAccessJarFile;
* *
* @author Phillip Webb * @author Phillip Webb
*/ */
public class JarFileArchive implements Archive { public class JarFileArchive extends Archive {
private final RandomAccessJarFile jarFile; private final RandomAccessJarFile jarFile;
...@@ -55,30 +56,40 @@ public class JarFileArchive implements Archive { ...@@ -55,30 +56,40 @@ public class JarFileArchive implements Archive {
this.entries = Collections.unmodifiableList(jarFileEntries); this.entries = Collections.unmodifiableList(jarFileEntries);
} }
@Override
public URL getUrl() throws MalformedURLException {
return this.jarFile.getUrl();
}
@Override @Override
public Manifest getManifest() throws IOException { public Manifest getManifest() throws IOException {
return this.jarFile.getManifest(); return this.jarFile.getManifest();
} }
@Override @Override
public Iterable<Entry> getEntries() { public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
return this.entries; List<Archive> nestedArchives = new ArrayList<Archive>();
for (Entry entry : getEntries()) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
} }
@Override @Override
public URL getUrl() throws MalformedURLException { public Collection<Entry> getEntries() {
return this.jarFile.getUrl(); return Collections.unmodifiableCollection(this.entries);
} }
@Override protected Archive getNestedArchive(Entry entry) throws IOException {
public Archive getNestedArchive(Entry entry) throws IOException {
JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry(); JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
RandomAccessJarFile jarFile = this.jarFile.getNestedJarFile(jarEntry); RandomAccessJarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
return new JarFileArchive(jarFile); return new JarFileArchive(jarFile);
} }
@Override @Override
public Archive getFilteredArchive(final EntryFilter filter) throws IOException { public Archive getFilteredArchive(final EntryRenameFilter filter) throws IOException {
RandomAccessJarFile filteredJar = this.jarFile RandomAccessJarFile filteredJar = this.jarFile
.getFilteredJarFile(new JarEntryFilter() { .getFilteredJarFile(new JarEntryFilter() {
@Override @Override
......
/* /*
* Copyright 2012-2013 the original author or authors. * Copyright 2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -14,13 +14,11 @@ ...@@ -14,13 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader;
/** /**
* @author Dave Syer * Abstraction over logical Archives be they backed by a JAR file or unpacked into a
* folder.
*
* @see org.springframework.boot.loader.archive.Archive
*/ */
public interface ArchiveFilter { package org.springframework.boot.loader.archive;
public boolean isArchive(Archive.Entry entry);
}
...@@ -17,63 +17,72 @@ ...@@ -17,63 +17,72 @@
package org.springframework.boot.loader; package org.springframework.boot.loader;
import java.io.File; import java.io.File;
import java.io.IOException;
import org.junit.After; import org.junit.After;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
/** /**
* Tests for {@link PropertiesLauncher}.
*
* @author Dave Syer * @author Dave Syer
*/ */
public class PropertiesLauncherTests { public class PropertiesLauncherTests {
private PropertiesLauncher launcher = new PropertiesLauncher(); @Before
public void setup() throws IOException {
System.setProperty("loader.home",
new File("src/test/resources").getAbsolutePath());
}
@After @After
public void close() { public void close() {
System.clearProperty("loader.system");
System.clearProperty("loader.home"); System.clearProperty("loader.home");
System.clearProperty("loader.path"); System.clearProperty("loader.path");
System.clearProperty("loader.main"); System.clearProperty("loader.main");
System.clearProperty("loader.config.name"); System.clearProperty("loader.config.name");
System.clearProperty("loader.config.location"); System.clearProperty("loader.config.location");
System.clearProperty("loader.system");
} }
@Test @Test
public void testDefaultHome() { public void testDefaultHome() {
assertEquals(new File(System.getProperty("user.dir")), PropertiesLauncher launcher = new PropertiesLauncher();
this.launcher.getHomeDirectory()); assertEquals(new File(System.getProperty("loader.home")),
launcher.getHomeDirectory());
} }
@Test @Test
public void testUserSpecifiedMain() throws Exception { public void testUserSpecifiedMain() throws Exception {
this.launcher.initialize(new File(".")); PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("demo.Application", this.launcher.getMainClass(null)); assertEquals("demo.Application", launcher.getMainClass());
assertEquals(null, System.getProperty("loader.main")); assertEquals(null, System.getProperty("loader.main"));
} }
@Test @Test
public void testUserSpecifiedConfigName() throws Exception { public void testUserSpecifiedConfigName() throws Exception {
System.setProperty("loader.config.name", "foo"); System.setProperty("loader.config.name", "foo");
this.launcher.initialize(new File(".")); PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("my.Application", this.launcher.getMainClass(null)); assertEquals("my.Application", launcher.getMainClass());
assertEquals("[etc/]", ReflectionTestUtils.getField(this.launcher, "paths") assertEquals("[etc/]", ReflectionTestUtils.getField(launcher, "paths").toString());
.toString());
} }
@Test @Test
public void testSystemPropertySpecifiedMain() throws Exception { public void testSystemPropertySpecifiedMain() throws Exception {
System.setProperty("loader.main", "foo.Bar"); System.setProperty("loader.main", "foo.Bar");
this.launcher.initialize(new File(".")); PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("foo.Bar", this.launcher.getMainClass(null)); assertEquals("foo.Bar", launcher.getMainClass());
} }
@Test @Test
public void testSystemPropertiesSet() throws Exception { public void testSystemPropertiesSet() throws Exception {
System.setProperty("loader.system", "true"); System.setProperty("loader.system", "true");
this.launcher.initialize(new File(".")); new PropertiesLauncher();
assertEquals("demo.Application", System.getProperty("loader.main")); assertEquals("demo.Application", System.getProperty("loader.main"));
} }
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader; package org.springframework.boot.loader.archive;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
...@@ -33,7 +33,10 @@ import org.junit.Before; ...@@ -33,7 +33,10 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.Archive.Entry; import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.Entry;
import org.springframework.boot.loader.archive.ExplodedArchive;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
...@@ -126,7 +129,7 @@ public class ExplodedArchiveTests { ...@@ -126,7 +129,7 @@ public class ExplodedArchiveTests {
@Test @Test
public void getFilteredArchive() throws Exception { public void getFilteredArchive() throws Exception {
Archive filteredArchive = this.archive Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryFilter() { .getFilteredArchive(new Archive.EntryRenameFilter() {
@Override @Override
public String apply(String entryName, Entry entry) { public String apply(String entryName, Entry entry) {
if (entryName.equals("1.dat")) { if (entryName.equals("1.dat")) {
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.loader; package org.springframework.boot.loader.archive;
import java.io.File; import java.io.File;
import java.net.URL; import java.net.URL;
...@@ -25,9 +25,10 @@ import org.junit.Before; ...@@ -25,9 +25,10 @@ import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TemporaryFolder; import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.Archive; import org.springframework.boot.loader.TestJarCreator;
import org.springframework.boot.loader.JarFileArchive; import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.Archive.Entry; import org.springframework.boot.loader.archive.JarFileArchive;
import org.springframework.boot.loader.archive.Archive.Entry;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
...@@ -83,7 +84,7 @@ public class JarFileArchiveTests { ...@@ -83,7 +84,7 @@ public class JarFileArchiveTests {
@Test @Test
public void getFilteredArchive() throws Exception { public void getFilteredArchive() throws Exception {
Archive filteredArchive = this.archive Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryFilter() { .getFilteredArchive(new Archive.EntryRenameFilter() {
@Override @Override
public String apply(String entryName, Entry entry) { public String apply(String entryName, Entry entry) {
if (entryName.equals("1.dat")) { if (entryName.equals("1.dat")) {
......
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