Further re-organization of launcher code

This commit is contained in:
Phillip Webb 2013-09-24 15:30:22 -07:00
parent 0e0eb7d3fa
commit 15bc25dc29
17 changed files with 390 additions and 347 deletions

View File

@ -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>

View File

@ -1,104 +0,0 @@
/*
* 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 {
}
}

View File

@ -0,0 +1,106 @@
/*
* 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;
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.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 executable archive {@link Launcher}s.
*
* @author Phillip Webb
*/
public abstract class ExecutableArchiveLauncher extends Launcher {
private final Archive archive;
public ExecutableArchiveLauncher() {
try {
this.archive = createArchive();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
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());
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);
}
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;
}
/**
* 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 abstract boolean isNestedArchive(Archive.Entry entry);
/**
* Called to post-process archive entries before they are used. Implementations can
* add and remove entries.
* @param archives the archives
* @throws Exception
*/
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
}
}

View File

@ -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);
}
} }

View File

@ -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
*/ */

View File

@ -1,5 +1,5 @@
/* /*
* 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;
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);
}
/**
* @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));
}
} }
return lib; catch (Exception ex) {
} ex.printStackTrace();
System.exit(1);
/**
* 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
* @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;
} }
/** /**
* Create a classloader for the specified lib. * Create a classloader for the specified archives.
* @param lib the lib * @param archives the archives
* @return the classloader * @return the classloader
* @throws Exception * @throws Exception
*/ */
protected ClassLoader createClassLoader(List<Archive> lib) throws Exception { protected ClassLoader createClassLoader(List<Archive> archives) throws Exception {
URL[] urls = new URL[lib.size()]; List<URL> urls = new ArrayList<URL>(archives.size());
for (int i = 0; i < urls.length; i++) { for (Archive archive : archives) {
urls[i] = lib.get(i).getUrl(); urls.add(archive.getUrl());
} }
return createClassLoader(urls); return createClassLoader(urls.toArray(new URL[urls.size()]));
}
/**
* 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());
} }
/** /**
@ -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 {
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;
} }

View File

@ -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
*/ */

View File

@ -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;
* *
* @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,92 +109,28 @@ 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() {
return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME, return new File(SystemPropertyUtils.resolvePlaceholders(System.getProperty(HOME,
"${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 {
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 {
return path; return path;
} }
public static void main(String[] args) {
new PropertiesLauncher().launch(args);
}
} }

View File

@ -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 {
} }
@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 {
}); });
} }
public static void main(String[] args) {
new WarLauncher().launch(args);
}
} }

View File

@ -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 the manifest of the archive.
* @return the manifest
* @throws IOException
*/
Manifest getManifest() throws IOException;
/**
* Returns archive entries.
* @return the archive entries
*/
Iterable<Entry> getEntries();
/** /**
* Returns a URL that can be used to load the archive. * Returns a URL that can be used to load the archive.
* @return the archive URL * @return the archive URL
* @throws MalformedURLException * @throws MalformedURLException
*/ */
URL getUrl() throws MalformedURLException; public abstract URL getUrl() throws MalformedURLException;
/** /**
* Returns a nest archive from on the the contained entries. * Obtain the main class that should be used to launch the application. By default
* @param entry the entry (may be a directory or file) * this method uses a {@code Start-Class} manifest entry.
* @return the nested archive * @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 * @throws IOException
*/ */
Archive getNestedArchive(Entry entry) throws IOException; public abstract Manifest getManifest() throws IOException;
/**
* Returns all entries from the archive.
* @return the archive entries
*/
public abstract Collection<Entry> getEntries();
/**
* 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
*/
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 {
* @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 {
} }
/** /**
* 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

View File

@ -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;
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;
* *
* @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 {
} }
} }
@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 {
} }
@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());

View File

@ -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;
* *
* @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 {
this.entries = Collections.unmodifiableList(jarFileEntries); this.entries = Collections.unmodifiableList(jarFileEntries);
} }
@Override
public Manifest getManifest() throws IOException {
return this.jarFile.getManifest();
}
@Override
public Iterable<Entry> getEntries() {
return this.entries;
}
@Override @Override
public URL getUrl() throws MalformedURLException { public URL getUrl() throws MalformedURLException {
return this.jarFile.getUrl(); return this.jarFile.getUrl();
} }
@Override @Override
public Archive getNestedArchive(Entry entry) throws IOException { public Manifest getManifest() throws IOException {
return this.jarFile.getManifest();
}
@Override
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 Collection<Entry> getEntries() {
return Collections.unmodifiableCollection(this.entries);
}
protected 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

View File

@ -1,5 +1,5 @@
/* /*
* 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 @@
* 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);
}

View File

@ -17,7 +17,7 @@
/** /**
* Classes and interfaces to allows random access to a block of data. * 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; package org.springframework.boot.loader.data;

View File

@ -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"));
} }

View File

@ -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;
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 {
@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")) {

View File

@ -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;
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 {
@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")) {