Commit ea11dafc authored by Dave Syer's avatar Dave Syer

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
parent b7ed5bbc
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 09:46:33 EDT 2013 #Tue Oct 15 11:07:38 EDT 2013
git.commit.id.abbrev=be12635 git.commit.id.abbrev=d3fa609
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Ensure env vars are consulted for PATH\n 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=be1263500df0c363ad45c0ccf387e4c06e6f5c7d git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312
git.commit.message.short=Ensure env vars are consulted for PATH git.commit.message.short=Extend PropertiesLauncher to load nested archives
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=master
git.commit.time=2013-10-14T16\:07\:50-0400 git.commit.time=2013-10-15T10\:51\:03-0400
git.build.time=2013-10-15T09\:46\:33-0400 git.build.time=2013-10-15T11\:07\:38-0400
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 09:46:12 EDT 2013 #Tue Oct 15 11:07:33 EDT 2013
git.commit.id.abbrev=be12635 git.commit.id.abbrev=d3fa609
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Ensure env vars are consulted for PATH\n 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=be1263500df0c363ad45c0ccf387e4c06e6f5c7d git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312
git.commit.message.short=Ensure env vars are consulted for PATH git.commit.message.short=Extend PropertiesLauncher to load nested archives
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=master
git.commit.time=2013-10-14T16\:07\:50-0400 git.commit.time=2013-10-15T10\:51\:03-0400
git.build.time=2013-10-15T09\:46\:12-0400 git.build.time=2013-10-15T11\:07\:33-0400
...@@ -21,10 +21,13 @@ import java.io.FileInputStream; ...@@ -21,10 +21,13 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.net.URLConnection; import java.net.URLConnection;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -36,6 +39,7 @@ import org.springframework.boot.loader.archive.Archive; ...@@ -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.Entry;
import org.springframework.boot.loader.archive.Archive.EntryFilter; import org.springframework.boot.loader.archive.Archive.EntryFilter;
import org.springframework.boot.loader.archive.ExplodedArchive; 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.archive.JarFileArchive;
import org.springframework.boot.loader.util.SystemPropertyUtils; import org.springframework.boot.loader.util.SystemPropertyUtils;
...@@ -286,14 +290,30 @@ public class PropertiesLauncher extends Launcher { ...@@ -286,14 +290,30 @@ public class PropertiesLauncher extends Launcher {
@Override @Override
protected String getMainClass() throws Exception { protected String getMainClass() throws Exception {
if (System.getProperty(MAIN) != null) { String property = SystemPropertyUtils.getProperty(MAIN);
return SystemPropertyUtils.resolvePlaceholders(System.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)) { if (this.properties.containsKey(MAIN)) {
return SystemPropertyUtils.resolvePlaceholders(this.properties String mainClass = SystemPropertyUtils.resolvePlaceholders(this.properties
.getProperty(MAIN)); .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 @Override
...@@ -302,14 +322,7 @@ public class PropertiesLauncher extends Launcher { ...@@ -302,14 +322,7 @@ public class PropertiesLauncher extends Launcher {
for (String path : this.paths) { for (String path : this.paths) {
for (Archive archive : getClassPathArchives(path)) { for (Archive archive : getClassPathArchives(path)) {
List<Archive> nested = new ArrayList<Archive>( List<Archive> nested = new ArrayList<Archive>(
archive.getNestedArchives(new EntryFilter() { archive.getNestedArchives(new ArchiveEntryFilter()));
@Override
public boolean matches(Entry entry) {
return entry.isDirectory()
|| entry.getName().endsWith(".jar")
|| entry.getName().endsWith(".zip");
}
}));
nested.add(0, archive); nested.add(0, archive);
lib.addAll(nested); lib.addAll(nested);
} }
...@@ -318,7 +331,7 @@ public class PropertiesLauncher extends Launcher { ...@@ -318,7 +331,7 @@ public class PropertiesLauncher extends Launcher {
return lib; return lib;
} }
private List<Archive> getClassPathArchives(String path) { private List<Archive> getClassPathArchives(String path) throws Exception {
String root = cleanupPath(stripFileUrlPrefix(path)); String root = cleanupPath(stripFileUrlPrefix(path));
List<Archive> lib = new ArrayList<Archive>(); List<Archive> lib = new ArrayList<Archive>();
File file = new File(root); File file = new File(root);
...@@ -330,9 +343,46 @@ public class PropertiesLauncher extends Launcher { ...@@ -330,9 +343,46 @@ public class PropertiesLauncher extends Launcher {
Archive archive = new ExplodedArchive(file); Archive archive = new ExplodedArchive(file);
lib.add(archive); 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; 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, private void addParentClassLoaderEntries(List<Archive> lib) throws IOException,
URISyntaxException { URISyntaxException {
ClassLoader parentClassLoader = getClass().getClassLoader(); ClassLoader parentClassLoader = getClass().getClassLoader();
...@@ -366,4 +416,38 @@ public class PropertiesLauncher extends Launcher { ...@@ -366,4 +416,38 @@ public class PropertiesLauncher extends Launcher {
new PropertiesLauncher().launch(args); 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);
}
}
} }
/*
* 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;
}
});
}
}
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 09:45:47 EDT 2013 #Tue Oct 15 11:07:32 EDT 2013
git.commit.id.abbrev=be12635 git.commit.id.abbrev=d3fa609
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Ensure env vars are consulted for PATH\n 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=be1263500df0c363ad45c0ccf387e4c06e6f5c7d git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312
git.commit.message.short=Ensure env vars are consulted for PATH git.commit.message.short=Extend PropertiesLauncher to load nested archives
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=master
git.commit.time=2013-10-14T16\:07\:50-0400 git.commit.time=2013-10-15T10\:51\:03-0400
git.build.time=2013-10-15T09\:45\:47-0400 git.build.time=2013-10-15T11\:07\:32-0400
#Generated by Git-Commit-Id-Plugin #Generated by Git-Commit-Id-Plugin
#Tue Oct 15 09:46:13 EDT 2013 #Tue Oct 15 11:07:34 EDT 2013
git.commit.id.abbrev=be12635 git.commit.id.abbrev=d3fa609
git.commit.user.email=dsyer@gopivotal.com git.commit.user.email=dsyer@gopivotal.com
git.commit.message.full=Ensure env vars are consulted for PATH\n 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=be1263500df0c363ad45c0ccf387e4c06e6f5c7d git.commit.id=d3fa60955b06fe78bbf0c914928d794661aca312
git.commit.message.short=Ensure env vars are consulted for PATH git.commit.message.short=Extend PropertiesLauncher to load nested archives
git.commit.user.name=Dave Syer git.commit.user.name=Dave Syer
git.build.user.name=Dave Syer git.build.user.name=Dave Syer
git.build.user.email=dsyer@gopivotal.com git.build.user.email=dsyer@gopivotal.com
git.branch=master git.branch=master
git.commit.time=2013-10-14T16\:07\:50-0400 git.commit.time=2013-10-15T10\:51\:03-0400
git.build.time=2013-10-15T09\:46\:13-0400 git.build.time=2013-10-15T11\:07\:34-0400
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment