diff --git a/spring-boot-tools/spring-boot-loader/pom.xml b/spring-boot-tools/spring-boot-loader/pom.xml index 22ce873645a..1f72711acc2 100644 --- a/spring-boot-tools/spring-boot-loader/pom.xml +++ b/spring-boot-tools/spring-boot-loader/pom.xml @@ -13,14 +13,15 @@ ${basedir}/../.. - org.slf4j jcl-over-slf4j + test ch.qos.logback logback-classic + test diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java deleted file mode 100644 index 910e6a6db02..00000000000 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/AbstractLauncher.java +++ /dev/null @@ -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 lib = new ArrayList(); - 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 lib) throws Exception { - } - -} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java new file mode 100644 index 00000000000..1a4dbb0c7f6 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -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 getClassPathArchives() throws Exception { + List archives = new ArrayList( + 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 archives) throws Exception { + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index 91b47bfcbfb..3df18043be2 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -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 lib) throws Exception { - lib.add(0, archive); + protected void postProcessClassPathArchives(List archives) throws Exception { + archives.add(0, getArchive()); } + public static void main(String[] args) { + new JarLauncher().launch(args); + } } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java index 039ece5a58e..ba12d257556 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java @@ -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 */ diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java similarity index 58% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java index e5882ee4c02..ee9fd5ea370 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchHelper.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Launcher.java @@ -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"); * 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 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 findNestedArchives(Archive archive, ArchiveFilter filter) - throws Exception { - List lib = new ArrayList(); - 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); } - 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 - * @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"); + catch (Exception ex) { + ex.printStackTrace(); + System.exit(1); } - return mainClass; } /** - * Create a classloader for the specified lib. - * @param lib the lib + * Create a classloader for the specified archives. + * @param archives the archives * @return the classloader * @throws Exception */ - protected ClassLoader createClassLoader(List lib) throws Exception { - URL[] urls = new URL[lib.size()]; - for (int i = 0; i < urls.length; i++) { - urls[i] = lib.get(i).getUrl(); + protected ClassLoader createClassLoader(List archives) throws Exception { + List urls = new ArrayList(archives.size()); + for (Archive archive : archives) { + 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(); } - /** - * 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 getClassPathArchives() throws Exception; } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java index ba76c90d5d3..ac08a7fdd39 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/MainMethodRunner.java @@ -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 */ diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java index cf5dec1812c..b0a63e7ec70 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java @@ -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. * *

@@ -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 DEFAULT_PATHS = Arrays.asList("lib/"); + private final File home; + private List paths = new ArrayList(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 getLibrary(File home, List paths) throws Exception { - List lib = new ArrayList(); - 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 getClassPathArchives() throws Exception { + List lib = new ArrayList(); + 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); + } + } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index da57b306eac..4aaeab982c3 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -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 lib) throws Exception { - lib.add(0, filterArchive(archive)); + protected void postProcessClassPathArchives(List 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); + } } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java similarity index 55% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java index f7ca8a2e011..8bc7acbcc36 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/Archive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/Archive.java @@ -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 { - - /** - * Returns the manifest of the archive. - * @return the manifest - * @throws IOException - */ - Manifest getManifest() throws IOException; - - /** - * Returns archive entries. - * @return the archive entries - */ - Iterable getEntries(); +public abstract class Archive { /** * Returns a URL that can be used to load the archive. * @return the archive URL * @throws MalformedURLException */ - URL getUrl() throws MalformedURLException; + public abstract 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 + * 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 */ - 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 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 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 diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExplodedArchive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java similarity index 86% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExplodedArchive.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java index c98d0a8fe4a..5b56def4723 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExplodedArchive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java @@ -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 SKIPPED_NAMES = new HashSet(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 getEntries() { - return this.entries.values(); + public List getNestedArchives(EntryFilter filter) throws IOException { + List nestedArchives = new ArrayList(); + 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 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 filteredEntries = new LinkedHashMap(); for (Map.Entry entry : this.entries.entrySet()) { String filteredName = filter.apply(entry.getKey(), entry.getValue()); diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarFileArchive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java similarity index 80% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarFileArchive.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java index e4387f6e2e3..1e465a86848 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarFileArchive.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java @@ -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 Manifest getManifest() throws IOException { - return this.jarFile.getManifest(); - } - - @Override - public Iterable getEntries() { - return this.entries; - } - @Override public URL getUrl() throws MalformedURLException { return this.jarFile.getUrl(); } @Override - public Archive getNestedArchive(Entry entry) throws IOException { + public Manifest getManifest() throws IOException { + return this.jarFile.getManifest(); + } + + @Override + public List getNestedArchives(EntryFilter filter) throws IOException { + List nestedArchives = new ArrayList(); + for (Entry entry : getEntries()) { + if (filter.matches(entry)) { + nestedArchives.add(getNestedArchive(entry)); + } + } + return Collections.unmodifiableList(nestedArchives); + } + + @Override + public Collection getEntries() { + return Collections.unmodifiableCollection(this.entries); + } + + 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 diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/package-info.java similarity index 69% rename from spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java rename to spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/package-info.java index f3cfa0ca959..239e4576fa0 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ArchiveFilter.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/package-info.java @@ -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"); * 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 { +package org.springframework.boot.loader.archive; - public boolean isArchive(Archive.Entry entry); - -} diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/package-info.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/package-info.java index 8d0a098adca..ed4bfe93a7e 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/package-info.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/package-info.java @@ -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; diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java index 869a86a1ed8..fdef6de34db 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java @@ -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")); } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExplodedArchiveTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java similarity index 92% rename from spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExplodedArchiveTests.java rename to spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java index c8018cb3f66..0e89f9e3c7d 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/ExplodedArchiveTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java @@ -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")) { diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarFileArchiveTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java similarity index 89% rename from spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarFileArchiveTests.java rename to spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java index b880fb8bb07..c20ec468fb8 100644 --- a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/JarFileArchiveTests.java +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java @@ -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")) {