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
This commit is contained in:
Dave Syer
2013-10-15 10:50:59 -04:00
parent b7ed5bbc76
commit ea11dafcbd
6 changed files with 214 additions and 41 deletions

View File

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

View File

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

View File

@@ -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<Archive> nested = new ArrayList<Archive>(
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<Archive> getClassPathArchives(String path) {
private List<Archive> getClassPathArchives(String path) throws Exception {
String root = cleanupPath(stripFileUrlPrefix(path));
List<Archive> lib = new ArrayList<Archive>();
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<Archive> 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);
}
}
}

View File

@@ -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<Entry> getEntries() {
List<Entry> nested = new ArrayList<Entry>();
for (Entry entry : this.parent.getEntries()) {
if (this.filter.matches(entry)) {
nested.add(entry);
}
}
return Collections.unmodifiableList(nested);
}
@Override
public List<Archive> 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;
}
});
}
}

View File

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

View File

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