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

Further re-organization of launcher code

parent 0e0eb7d3
......@@ -13,14 +13,15 @@
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<!-- TODO: maybe put these in the parent? -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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");
* you may not use this file except in compliance with the License.
......@@ -22,44 +22,34 @@ import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
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
* classpath.
* Base class for executable archive {@link Launcher}s.
*
* @author Phillip Webb
* @author Dave Syer
*/
public abstract class AbstractLauncher implements ArchiveFilter {
private Logger logger = Logger.getLogger(AbstractLauncher.class.getName());
public abstract class ExecutableArchiveLauncher extends Launcher {
private LaunchHelper helper = new LaunchHelper();
private final Archive archive;
/**
* 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) {
public ExecutableArchiveLauncher() {
try {
launch(args, getClass().getProtectionDomain());
this.archive = createArchive();
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
throw new IllegalStateException(ex);
}
}
/**
* 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 {
private Archive createArchive() throws Exception {
ProtectionDomain protectionDomain = getClass().getProtectionDomain();
CodeSource codeSource = protectionDomain.getCodeSource();
URI location = (codeSource == null ? null : codeSource.getLocation().toURI());
String path = (location == null ? null : location.getPath());
......@@ -71,34 +61,46 @@ public abstract class AbstractLauncher implements ArchiveFilter {
throw new IllegalStateException(
"Unable to determine code source archive from " + root);
}
Archive archive = (root.isDirectory() ? new ExplodedArchive(root)
: new JarFileArchive(root));
launch(args, archive);
return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root));
}
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
* @param args the incoming arguments
* @param archive the underlying (zip/war/jar) archive
* @throws Exception
* 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 entry the jar entry
* @return {@code true} if the entry is a nested item (jar or folder)
*/
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);
}
protected abstract boolean isNestedArchive(Archive.Entry entry);
/**
* 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
* Called to post-process archive entries before they are used. Implementations can
* add and remove entries.
* @param archives the archives
* @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;
import java.util.List;
import org.springframework.boot.loader.archive.Archive;
/**
* {@link AbstractLauncher} for JAR based archives. This launcher assumes that dependency
* jars are included inside a {@code /lib} directory.
* {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are
* included inside a {@code /lib} directory.
*
* @author Phillip Webb
*/
public class JarLauncher extends AbstractLauncher {
public static void main(String[] args) {
new JarLauncher().launch(args);
}
public class JarLauncher extends ExecutableArchiveLauncher {
@Override
public boolean isArchive(Archive.Entry entry) {
protected boolean isNestedArchive(Archive.Entry entry) {
return !entry.isDirectory() && entry.getName().startsWith("lib/");
}
@Override
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
lib.add(0, archive);
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
archives.add(0, getArchive());
}
public static void main(String[] args) {
new JarLauncher().launch(args);
}
}
......@@ -25,7 +25,7 @@ import java.security.PrivilegedExceptionAction;
import org.springframework.boot.loader.jar.RandomAccessJarFile;
/**
* {@link ClassLoader} used by the {@link AbstractLauncher}.
* {@link ClassLoader} used by the {@link Launcher}.
*
* @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");
* you may not use this file except in compliance with the License.
......@@ -22,79 +22,64 @@ import java.util.ArrayList;
import java.util.List;
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
*/
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
* directly referenced.
*/
private static final String RUNNER_CLASS = AbstractLauncher.class.getPackage()
.getName() + ".MainMethodRunner";
private static final String RUNNER_CLASS = Launcher.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
* @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)
throws Exception {
ClassLoader classLoader = createClassLoader(lib);
launch(args, mainClass, classLoader);
}
/**
* @param archive the archive to search
* @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));
}
protected void launch(String[] args) {
try {
ClassLoader classLoader = createClassLoader(getClassPathArchives());
launch(args, getMainClass(), classLoader);
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
}
return lib;
}
/**
* 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 archives.
* @param archives the archives
* @return the classloader
* @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");
protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
List<URL> urls = new ArrayList<URL>(archives.size());
for (Archive archive : archives) {
urls.add(archive.getUrl());
}
return mainClass;
return createClassLoader(urls.toArray(new URL[urls.size()]));
}
/**
* Create a classloader for the specified lib.
* @param lib the lib
* Create a classloader for the specified URLs
* @param urls the URLs
* @return the classloader
* @throws Exception
*/
protected ClassLoader createClassLoader(List<Archive> lib) throws Exception {
URL[] urls = new URL[lib.size()];
for (int i = 0; i < urls.length; i++) {
urls[i] = lib.get(i).getUrl();
}
return createClassLoader(urls);
protected ClassLoader createClassLoader(URL[] urls) throws Exception {
return new LaunchedURLClassLoader(urls, getClass().getClassLoader());
}
/**
......@@ -113,16 +98,6 @@ public class LaunchHelper {
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.
* @param mainClass the main class
......@@ -139,4 +114,17 @@ public class LaunchHelper {
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;
import java.lang.reflect.Method;
/**
* 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.
* 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 class loader.
*
* @author Phillip Webb
*/
......
......@@ -30,11 +30,15 @@ import java.util.List;
import java.util.Properties;
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;
/**
* {@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
* {@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
* well-behaved OS-level services than a model based on executable jars.
*
* <p>
......@@ -60,9 +64,9 @@ import org.springframework.boot.loader.util.SystemPropertyUtils;
*
* @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
......@@ -105,92 +109,28 @@ public class PropertiesLauncher implements ArchiveFilter {
private static final List<String> DEFAULT_PATHS = Arrays.asList("lib/");
private final File home;
private List<String> paths = new ArrayList<String>(DEFAULT_PATHS);
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) {
public PropertiesLauncher() {
try {
File home = getHomeDirectory();
initialize(home);
this.helper.launch(args, getMainClass(home), getLibrary(home, this.paths));
this.home = getHomeDirectory();
initializeProperties(this.home);
initializePaths();
}
catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
throw new IllegalStateException(ex);
}
}
@Override
public boolean isArchive(Archive.Entry entry) {
return entry.isDirectory() || isArchive(entry.getName());
}
protected File getHomeDirectory() {
return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
"${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 {
String config = SystemPropertyUtils.resolvePlaceholders(System.getProperty(
CONFIG_NAME, "application")) + ".properties";
......@@ -346,6 +286,46 @@ public class PropertiesLauncher implements ArchiveFilter {
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) {
path = path.trim();
// Always a directory
......@@ -359,4 +339,8 @@ public class PropertiesLauncher implements ArchiveFilter {
return path;
}
public static void main(String[] args) {
new PropertiesLauncher().launch(args);
}
}
......@@ -19,21 +19,19 @@ package org.springframework.boot.loader;
import java.io.IOException;
import java.util.List;
import org.springframework.boot.loader.archive.Archive;
/**
* {@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}.
* {@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}.
*
* @author Phillip Webb
*/
public class WarLauncher extends AbstractLauncher {
public static void main(String[] args) {
new WarLauncher().launch(args);
}
public class WarLauncher extends ExecutableArchiveLauncher {
@Override
public boolean isArchive(Archive.Entry entry) {
public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
return entry.getName().equals("WEB-INF/classes/");
}
......@@ -44,20 +42,18 @@ public class WarLauncher extends AbstractLauncher {
}
@Override
protected void postProcessLib(Archive archive, List<Archive> lib) throws Exception {
lib.add(0, filterArchive(archive));
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
archives.add(0, getFilteredArchive());
}
/**
* Filter the specified WAR file to exclude elements that should not appear on the
* classpath.
* @param archive the source archive
* @return the filtered archive
* @throws IOException on error
*/
protected Archive filterArchive(Archive archive) throws IOException {
return archive.getFilteredArchive(new Archive.EntryFilter() {
protected Archive getFilteredArchive() throws IOException {
return getArchive().getFilteredArchive(new Archive.EntryRenameFilter() {
@Override
public String apply(String entryName, Archive.Entry entry) {
if (entryName.startsWith("META-INF/") || entryName.startsWith("WEB-INF/")) {
......@@ -68,4 +64,7 @@ public class WarLauncher extends AbstractLauncher {
});
}
public static void main(String[] args) {
new WarLauncher().launch(args);
}
}
......@@ -14,48 +14,67 @@
* limitations under the License.
*/
package org.springframework.boot.loader;
package org.springframework.boot.loader.archive;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
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
* @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.
* @return the manifest
* @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
*/
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.
* @param entry the entry (may be a directory or file)
* @return the nested archive
* Returns nested {@link Archive}s for entries that match the specified filter.
* @param filter the filter used to limit entries
* @return nested archives
* @throws IOException
*/
Archive getNestedArchive(Entry entry) throws IOException;
public abstract List<Archive> getNestedArchives(EntryFilter filter)
throws IOException;
/**
* Returns a filtered version of the archive.
......@@ -63,7 +82,8 @@ public interface Archive {
* @return a filter archive
* @throws IOException
*/
Archive getFilteredArchive(EntryFilter filter) throws IOException;
public abstract Archive getFilteredArchive(EntryRenameFilter filter)
throws IOException;
/**
* Represents a single entry in the archive.
......@@ -85,10 +105,24 @@ public interface Archive {
}
/**
* A filter for archive entries.
* Strategy interface to filter {@link Entry Entries}.
*/
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.
* @param entryName the current entry name. This may be different that the
......
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.loader;
package org.springframework.boot.loader.archive;
import java.io.File;
import java.io.FileInputStream;
......@@ -24,10 +24,13 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
......@@ -37,7 +40,7 @@ import java.util.jar.Manifest;
*
* @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(
".", ".."));
......@@ -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
public Manifest getManifest() throws IOException {
if (this.manifest == null && this.entries.containsKey(MANIFEST_ENTRY_NAME)) {
......@@ -98,25 +107,28 @@ public class ExplodedArchive implements Archive {
}
@Override
public Iterable<Entry> getEntries() {
return this.entries.values();
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<Archive>();
for (Entry entry : getEntries()) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
@Override
public URL getUrl() throws MalformedURLException {
FilteredURLStreamHandler handler = new FilteredURLStreamHandler();
return new URL("file", "", -1, this.root.getAbsolutePath() + "/", handler);
// return this.root.toURI().toURL();
public Collection<Entry> getEntries() {
return Collections.unmodifiableCollection(this.entries.values());
}
@Override
public Archive getNestedArchive(Entry entry) throws IOException {
protected Archive getNestedArchive(Entry entry) throws IOException {
File file = ((FileEntry) entry).getFile();
return (file.isDirectory() ? new ExplodedArchive(file) : new JarFileArchive(file));
}
@Override
public Archive getFilteredArchive(EntryFilter filter) throws IOException {
public Archive getFilteredArchive(EntryRenameFilter filter) throws IOException {
Map<String, Entry> filteredEntries = new LinkedHashMap<String, Archive.Entry>();
for (Map.Entry<String, Entry> entry : this.entries.entrySet()) {
String filteredName = filter.apply(entry.getKey(), entry.getValue());
......
......@@ -14,13 +14,14 @@
* limitations under the License.
*/
package org.springframework.boot.loader;
package org.springframework.boot.loader.archive;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
......@@ -35,7 +36,7 @@ import org.springframework.boot.loader.jar.RandomAccessJarFile;
*
* @author Phillip Webb
*/
public class JarFileArchive implements Archive {
public class JarFileArchive extends Archive {
private final RandomAccessJarFile jarFile;
......@@ -55,30 +56,40 @@ public class JarFileArchive implements Archive {
this.entries = Collections.unmodifiableList(jarFileEntries);
}
@Override
public URL getUrl() throws MalformedURLException {
return this.jarFile.getUrl();
}
@Override
public Manifest getManifest() throws IOException {
return this.jarFile.getManifest();
}
@Override
public Iterable<Entry> getEntries() {
return this.entries;
public List<Archive> getNestedArchives(EntryFilter filter) throws IOException {
List<Archive> nestedArchives = new ArrayList<Archive>();
for (Entry entry : getEntries()) {
if (filter.matches(entry)) {
nestedArchives.add(getNestedArchive(entry));
}
}
return Collections.unmodifiableList(nestedArchives);
}
@Override
public URL getUrl() throws MalformedURLException {
return this.jarFile.getUrl();
public Collection<Entry> getEntries() {
return Collections.unmodifiableCollection(this.entries);
}
@Override
public Archive getNestedArchive(Entry entry) throws IOException {
protected Archive getNestedArchive(Entry entry) throws IOException {
JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry();
RandomAccessJarFile jarFile = this.jarFile.getNestedJarFile(jarEntry);
return new JarFileArchive(jarFile);
}
@Override
public Archive getFilteredArchive(final EntryFilter filter) throws IOException {
public Archive getFilteredArchive(final EntryRenameFilter filter) throws IOException {
RandomAccessJarFile filteredJar = this.jarFile
.getFilteredJarFile(new JarEntryFilter() {
@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");
* you may not use this file except in compliance with the License.
......@@ -14,13 +14,11 @@
* 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 {
public boolean isArchive(Archive.Entry entry);
package org.springframework.boot.loader.archive;
}
......@@ -17,7 +17,7 @@
/**
* Classes and interfaces to allows random access to a block of data.
*
* @see org.springframework.boot.loader.data.RandomAccessData
* @see org.springframework.boot.loader.data.RandomAccessData
*/
package org.springframework.boot.loader.data;
......@@ -17,63 +17,72 @@
package org.springframework.boot.loader;
import java.io.File;
import java.io.IOException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
import static org.junit.Assert.assertEquals;
/**
* Tests for {@link PropertiesLauncher}.
*
* @author Dave Syer
*/
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
public void close() {
System.clearProperty("loader.system");
System.clearProperty("loader.home");
System.clearProperty("loader.path");
System.clearProperty("loader.main");
System.clearProperty("loader.config.name");
System.clearProperty("loader.config.location");
System.clearProperty("loader.system");
}
@Test
public void testDefaultHome() {
assertEquals(new File(System.getProperty("user.dir")),
this.launcher.getHomeDirectory());
PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals(new File(System.getProperty("loader.home")),
launcher.getHomeDirectory());
}
@Test
public void testUserSpecifiedMain() throws Exception {
this.launcher.initialize(new File("."));
assertEquals("demo.Application", this.launcher.getMainClass(null));
PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("demo.Application", launcher.getMainClass());
assertEquals(null, System.getProperty("loader.main"));
}
@Test
public void testUserSpecifiedConfigName() throws Exception {
System.setProperty("loader.config.name", "foo");
this.launcher.initialize(new File("."));
assertEquals("my.Application", this.launcher.getMainClass(null));
assertEquals("[etc/]", ReflectionTestUtils.getField(this.launcher, "paths")
.toString());
PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("my.Application", launcher.getMainClass());
assertEquals("[etc/]", ReflectionTestUtils.getField(launcher, "paths").toString());
}
@Test
public void testSystemPropertySpecifiedMain() throws Exception {
System.setProperty("loader.main", "foo.Bar");
this.launcher.initialize(new File("."));
assertEquals("foo.Bar", this.launcher.getMainClass(null));
PropertiesLauncher launcher = new PropertiesLauncher();
assertEquals("foo.Bar", launcher.getMainClass());
}
@Test
public void testSystemPropertiesSet() throws Exception {
System.setProperty("loader.system", "true");
this.launcher.initialize(new File("."));
new PropertiesLauncher();
assertEquals("demo.Application", System.getProperty("loader.main"));
}
......
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.loader;
package org.springframework.boot.loader.archive;
import java.io.File;
import java.io.FileOutputStream;
......@@ -33,7 +33,10 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
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.nullValue;
......@@ -126,7 +129,7 @@ public class ExplodedArchiveTests {
@Test
public void getFilteredArchive() throws Exception {
Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryFilter() {
.getFilteredArchive(new Archive.EntryRenameFilter() {
@Override
public String apply(String entryName, Entry entry) {
if (entryName.equals("1.dat")) {
......
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.loader;
package org.springframework.boot.loader.archive;
import java.io.File;
import java.net.URL;
......@@ -25,9 +25,10 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.boot.loader.Archive;
import org.springframework.boot.loader.JarFileArchive;
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.JarFileArchive;
import org.springframework.boot.loader.archive.Archive.Entry;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
......@@ -83,7 +84,7 @@ public class JarFileArchiveTests {
@Test
public void getFilteredArchive() throws Exception {
Archive filteredArchive = this.archive
.getFilteredArchive(new Archive.EntryFilter() {
.getFilteredArchive(new Archive.EntryRenameFilter() {
@Override
public String apply(String entryName, Entry entry) {
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