From ea11dafcbd951b3b23257585bce1f479ca9faa73 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Tue, 15 Oct 2013 10:50:59 -0400 Subject: [PATCH] Extend PropertiesLauncher to load nested archives PropertiesLauncher can now be used to run an executable jar, and by default it will pick up nested archives in lib/ (where the Boot tools puts them). User can provide loader.path (colon-separated) to change the nested path. [#58837492] [bs-330] Add tooling for PropertiesLauncher --- .../org/springframework/boot/git.properties | 14 +-- .../org/springframework/boot/git.properties | 14 +-- .../boot/loader/PropertiesLauncher.java | 110 +++++++++++++++--- .../boot/loader/archive/FilteredArchive.java | 89 ++++++++++++++ .../org/springframework/boot/git.properties | 14 +-- .../org/springframework/boot/git.properties | 14 +-- 6 files changed, 214 insertions(+), 41 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/FilteredArchive.java diff --git a/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/org/springframework/boot/git.properties b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/org/springframework/boot/git.properties index b1d21be0ec..4c71ec6a6b 100644 --- a/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/org/springframework/boot/git.properties +++ b/spring-boot-tools/spring-boot-gradle-plugin/src/main/resources/org/springframework/boot/git.properties @@ -1,13 +1,13 @@ #Generated by Git-Commit-Id-Plugin -#Tue Oct 15 09:46:33 EDT 2013 -git.commit.id.abbrev=be12635 +#Tue Oct 15 11:07:38 EDT 2013 +git.commit.id.abbrev=d3fa609 git.commit.user.email=dsyer@gopivotal.com -git.commit.message.full=Ensure env vars are consulted for PATH\n -git.commit.id=be1263500df0c363ad45c0ccf387e4c06e6f5c7d -git.commit.message.short=Ensure env vars are consulted for PATH +git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n +git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 +git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.user.name=Dave Syer git.build.user.name=Dave Syer git.build.user.email=dsyer@gopivotal.com git.branch=master -git.commit.time=2013-10-14T16\:07\:50-0400 -git.build.time=2013-10-15T09\:46\:33-0400 +git.commit.time=2013-10-15T10\:51\:03-0400 +git.build.time=2013-10-15T11\:07\:38-0400 diff --git a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/git.properties b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/git.properties index 020deee2b2..c284d0a7ad 100644 --- a/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/git.properties +++ b/spring-boot-tools/spring-boot-loader-tools/src/main/resources/org/springframework/boot/git.properties @@ -1,13 +1,13 @@ #Generated by Git-Commit-Id-Plugin -#Tue Oct 15 09:46:12 EDT 2013 -git.commit.id.abbrev=be12635 +#Tue Oct 15 11:07:33 EDT 2013 +git.commit.id.abbrev=d3fa609 git.commit.user.email=dsyer@gopivotal.com -git.commit.message.full=Ensure env vars are consulted for PATH\n -git.commit.id=be1263500df0c363ad45c0ccf387e4c06e6f5c7d -git.commit.message.short=Ensure env vars are consulted for PATH +git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n +git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 +git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.user.name=Dave Syer git.build.user.name=Dave Syer git.build.user.email=dsyer@gopivotal.com git.branch=master -git.commit.time=2013-10-14T16\:07\:50-0400 -git.build.time=2013-10-15T09\:46\:12-0400 +git.commit.time=2013-10-15T10\:51\:03-0400 +git.build.time=2013-10-15T11\:07\:33-0400 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 2d0fb8154d..7ade4f0119 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 @@ -21,10 +21,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; +import java.security.CodeSource; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -36,6 +39,7 @@ 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.FilteredArchive; import org.springframework.boot.loader.archive.JarFileArchive; import org.springframework.boot.loader.util.SystemPropertyUtils; @@ -286,14 +290,30 @@ public class PropertiesLauncher extends Launcher { @Override protected String getMainClass() throws Exception { - if (System.getProperty(MAIN) != null) { - return SystemPropertyUtils.resolvePlaceholders(System.getProperty(MAIN)); + String property = SystemPropertyUtils.getProperty(MAIN); + if (property != null) { + String mainClass = SystemPropertyUtils.resolvePlaceholders(property); + this.logger.info("Main class from environment: " + mainClass); + return mainClass; } if (this.properties.containsKey(MAIN)) { - return SystemPropertyUtils.resolvePlaceholders(this.properties + String mainClass = SystemPropertyUtils.resolvePlaceholders(this.properties .getProperty(MAIN)); + this.logger.info("Main class from properties: " + mainClass); + return mainClass; + } + try { + // Prefer home dir for MANIFEST if there is one + String mainClass = new ExplodedArchive(this.home).getMainClass(); + this.logger.info("Main class from home directory manifest: " + mainClass); + return mainClass; + } + catch (IllegalStateException e) { + // Otherwise try the parent archive + String mainClass = createArchive().getMainClass(); + this.logger.info("Main class from archive manifest: " + mainClass); + return mainClass; } - return new ExplodedArchive(this.home).getMainClass(); } @Override @@ -302,14 +322,7 @@ public class PropertiesLauncher extends Launcher { for (String path : this.paths) { for (Archive archive : getClassPathArchives(path)) { List nested = new ArrayList( - archive.getNestedArchives(new EntryFilter() { - @Override - public boolean matches(Entry entry) { - return entry.isDirectory() - || entry.getName().endsWith(".jar") - || entry.getName().endsWith(".zip"); - } - })); + archive.getNestedArchives(new ArchiveEntryFilter())); nested.add(0, archive); lib.addAll(nested); } @@ -318,7 +331,7 @@ public class PropertiesLauncher extends Launcher { return lib; } - private List getClassPathArchives(String path) { + private List getClassPathArchives(String path) throws Exception { String root = cleanupPath(stripFileUrlPrefix(path)); List lib = new ArrayList(); File file = new File(root); @@ -330,9 +343,46 @@ public class PropertiesLauncher extends Launcher { Archive archive = new ExplodedArchive(file); lib.add(archive); } + Archive nested = getNestedArchive(root); + if (nested != null) { + this.logger.info("Adding classpath entries from nested " + nested.getUrl() + + root); + lib.add(nested); + } return lib; } + private Archive getNestedArchive(final String root) throws Exception { + Archive parent = createArchive(); + if (root.startsWith("/") || parent.getUrl().equals(this.home.toURI().toURL())) { + // If home dir is same as parent archive, no need to add it twice. + return null; + } + EntryFilter filter = new PrefixMatchingArchiveFilter(root); + if (parent.getNestedArchives(filter).isEmpty()) { + return null; + } + // If there are more archives nested in this subdirectory (root) then create a new + // virtual archive for them, and have it added to the classpath + return new FilteredArchive(parent, filter); + } + + 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)); + } + private void addParentClassLoaderEntries(List lib) throws IOException, URISyntaxException { ClassLoader parentClassLoader = getClass().getClassLoader(); @@ -366,4 +416,38 @@ public class PropertiesLauncher extends Launcher { new PropertiesLauncher().launch(args); } + /** + * Convenience class for finding nested archives (archive entries that can be + * classpath entries). + * + * @author Dave Syer + */ + private static final class ArchiveEntryFilter implements EntryFilter { + @Override + public boolean matches(Entry entry) { + return entry.isDirectory() || entry.getName().endsWith(".jar") + || entry.getName().endsWith(".zip"); + } + } + + /** + * Convenience class for finding nested archives that have a prefix in their file path + * (e.g. "lib/"). + * + * @author Dave Syer + */ + private static final class PrefixMatchingArchiveFilter implements EntryFilter { + private final String prefix; + private final ArchiveEntryFilter filter = new ArchiveEntryFilter(); + + private PrefixMatchingArchiveFilter(String prefix) { + this.prefix = prefix; + } + + @Override + public boolean matches(Entry entry) { + return entry.getName().startsWith(this.prefix) && this.filter.matches(entry); + } + } + } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/FilteredArchive.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/FilteredArchive.java new file mode 100644 index 0000000000..cc4a84ac54 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/archive/FilteredArchive.java @@ -0,0 +1,89 @@ +/* + * 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.archive; + +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.List; +import java.util.jar.Manifest; + +/** + * @author Dave Syer + */ +public class FilteredArchive extends Archive { + + private Archive parent; + private EntryFilter filter; + + public FilteredArchive(Archive parent, EntryFilter filter) { + this.parent = parent; + this.filter = filter; + } + + @Override + public URL getUrl() throws MalformedURLException { + return this.parent.getUrl(); + } + + @Override + public String getMainClass() throws Exception { + return this.parent.getMainClass(); + } + + @Override + public Manifest getManifest() throws IOException { + return this.parent.getManifest(); + } + + @Override + public Collection getEntries() { + List nested = new ArrayList(); + for (Entry entry : this.parent.getEntries()) { + if (this.filter.matches(entry)) { + nested.add(entry); + } + } + return Collections.unmodifiableList(nested); + } + + @Override + public List getNestedArchives(final EntryFilter filter) throws IOException { + return this.parent.getNestedArchives(new EntryFilter() { + @Override + public boolean matches(Entry entry) { + return FilteredArchive.this.filter.matches(entry) + && filter.matches(entry); + } + }); + } + + @Override + public Archive getFilteredArchive(final EntryRenameFilter filter) throws IOException { + return this.parent.getFilteredArchive(new EntryRenameFilter() { + @Override + public String apply(String entryName, Entry entry) { + return FilteredArchive.this.filter.matches(entry) ? filter.apply( + entryName, entry) : null; + } + }); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/main/resources/org/springframework/boot/git.properties b/spring-boot-tools/spring-boot-loader/src/main/resources/org/springframework/boot/git.properties index a2c6cdffe3..35fc5e979a 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/resources/org/springframework/boot/git.properties +++ b/spring-boot-tools/spring-boot-loader/src/main/resources/org/springframework/boot/git.properties @@ -1,13 +1,13 @@ #Generated by Git-Commit-Id-Plugin -#Tue Oct 15 09:45:47 EDT 2013 -git.commit.id.abbrev=be12635 +#Tue Oct 15 11:07:32 EDT 2013 +git.commit.id.abbrev=d3fa609 git.commit.user.email=dsyer@gopivotal.com -git.commit.message.full=Ensure env vars are consulted for PATH\n -git.commit.id=be1263500df0c363ad45c0ccf387e4c06e6f5c7d -git.commit.message.short=Ensure env vars are consulted for PATH +git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n +git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 +git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.user.name=Dave Syer git.build.user.name=Dave Syer git.build.user.email=dsyer@gopivotal.com git.branch=master -git.commit.time=2013-10-14T16\:07\:50-0400 -git.build.time=2013-10-15T09\:45\:47-0400 +git.commit.time=2013-10-15T10\:51\:03-0400 +git.build.time=2013-10-15T11\:07\:32-0400 diff --git a/spring-boot-tools/spring-boot-maven-plugin/src/main/resources/org/springframework/boot/git.properties b/spring-boot-tools/spring-boot-maven-plugin/src/main/resources/org/springframework/boot/git.properties index 501bb10374..1bd00a35e8 100644 --- a/spring-boot-tools/spring-boot-maven-plugin/src/main/resources/org/springframework/boot/git.properties +++ b/spring-boot-tools/spring-boot-maven-plugin/src/main/resources/org/springframework/boot/git.properties @@ -1,13 +1,13 @@ #Generated by Git-Commit-Id-Plugin -#Tue Oct 15 09:46:13 EDT 2013 -git.commit.id.abbrev=be12635 +#Tue Oct 15 11:07:34 EDT 2013 +git.commit.id.abbrev=d3fa609 git.commit.user.email=dsyer@gopivotal.com -git.commit.message.full=Ensure env vars are consulted for PATH\n -git.commit.id=be1263500df0c363ad45c0ccf387e4c06e6f5c7d -git.commit.message.short=Ensure env vars are consulted for PATH +git.commit.message.full=Extend PropertiesLauncher to load nested archives\n\nPropertiesLauncher can now be used to run an executable jar, and by\ndefault it will pick up nested archives in lib/ (where the Boot\ntools puts them). User can provide loader.path (colon-separated)\nto change the nested path.\n\n[\#58837492] [bs-330] Add tooling for PropertiesLauncher\n +git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312 +git.commit.message.short=Extend PropertiesLauncher to load nested archives git.commit.user.name=Dave Syer git.build.user.name=Dave Syer git.build.user.email=dsyer@gopivotal.com git.branch=master -git.commit.time=2013-10-14T16\:07\:50-0400 -git.build.time=2013-10-15T09\:46\:13-0400 +git.commit.time=2013-10-15T10\:51\:03-0400 +git.build.time=2013-10-15T11\:07\:34-0400