diff --git a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc index 7a57cd6471d..1a0732358d7 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-executable-jar-format.adoc @@ -147,7 +147,8 @@ files in directories (as opposed to explicitly on the classpath). In the case of you just add extra jars in those locations if you want more. The `PropertiesLauncher` looks in `BOOT-INF/lib/` in your application archive by default, but you can add additional locations by setting an environment variable `LOADER_PATH` or `loader.path` -in `loader.properties` (comma-separated list of directories or archives). +in `loader.properties` (comma-separated list of directories, archives, or directories +within archives). @@ -280,7 +281,8 @@ the `Main-Class` attribute and leave out `Start-Class`. * `loader.home` is only the directory location of an additional properties file (overriding the default) as long as `loader.config.location` is not specified. * `loader.path` can contain directories (scanned recursively for jar and zip files), - archive paths, or wildcard patterns (for the default JVM behavior). + archive paths, a directory within an archive that is scanned for jar files (for + example, `dependencies.jar!/lib`), or wildcard patterns (for the default JVM behavior). * `loader.path` (if empty) defaults to `BOOT-INF/lib` (meaning a local directory or a nested one if running from an archive). Because of this `PropertiesLauncher` behaves the same as `JarLauncher` when no additional configuration is provided. diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties new file mode 100644 index 00000000000..48a576ba043 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/loader.properties @@ -0,0 +1 @@ +loader.path=jar:file:target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-dependencies.jar/!BOOT-INF/lib \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml new file mode 100644 index 00000000000..50b0f033e9b --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/pom.xml @@ -0,0 +1,104 @@ + + + 4.0.0 + org.springframework.boot.launcher.it + executable-props-lib + 0.0.1.BUILD-SNAPSHOT + + UTF-8 + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack + prepare-package + + unpack + + + + + @project.groupId@ + @project.artifactId@ + @project.version@ + jar + + + ${project.build.directory}/app-assembly + + + + copy + prepare-package + + copy-dependencies + + + ${project.build.directory}/dependencies-assembly/BOOT-INF/lib + + + + + + maven-assembly-plugin + 2.4 + + + app + package + + single + + + + src/main/assembly/app.xml + + + + org.springframework.boot.loader.PropertiesLauncher + + + org.springframework.boot.launcher.it.props.EmbeddedJarStarter + + + + + + depedendencies + package + + single + + + + src/main/assembly/dependencies.xml + + + + + + + + + + org.springframework + spring-context + 4.1.4.RELEASE + + + diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml new file mode 100644 index 00000000000..d332aebc34d --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/app.xml @@ -0,0 +1,27 @@ + + + app + + jar + + false + + + + + ${project.groupId}:${project.artifactId} + + BOOT-INF/classes + true + + + + + ${project.build.directory}/app-assembly + / + + + diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml new file mode 100644 index 00000000000..5044392b3e9 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/assembly/dependencies.xml @@ -0,0 +1,17 @@ + + + dependencies + + jar + + false + + + ${project.build.directory}/dependencies-assembly + / + + + diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java new file mode 100644 index 00000000000..da2c1bf2a90 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/EmbeddedJarStarter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2017 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.launcher.it.props; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * Main class to start the embedded server. + * + * @author Dave Syer + */ +public final class EmbeddedJarStarter { + + public static void main(String[] args) throws Exception { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class); + context.getBean(SpringConfiguration.class).run(args); + context.close(); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java new file mode 100644 index 00000000000..f05bae73fdc --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/java/org/springframework/boot/launcher/it/props/SpringConfiguration.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2017 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.launcher.it.props; + +import java.io.IOException; +import java.util.Properties; + +import javax.annotation.PostConstruct; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +/** + * Spring configuration. + * + * @author Dave Syer + */ +@Configuration +@ComponentScan +public class SpringConfiguration { + + private String message = "Jar"; + + @PostConstruct + public void init() throws IOException { + Properties props = new Properties(); + props.load(new ClassPathResource("application.properties").getInputStream()); + String value = props.getProperty("message"); + if (value!=null) { + this.message = value; + } + + } + + public void run(String... args) { + System.err.println("Hello Embedded " + this.message + "!"); + } + +} diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties new file mode 100644 index 00000000000..c11051e3477 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/src/main/resources/application.properties @@ -0,0 +1 @@ +message: World \ No newline at end of file diff --git a/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy new file mode 100644 index 00000000000..b855fbe7c7a --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/it/executable-props-lib/verify.groovy @@ -0,0 +1,17 @@ +def jarfile = './target/executable-props-lib-0.0.1.BUILD-SNAPSHOT-app.jar' + +new File("${basedir}/application.properties").delete() + +String exec(String command) { + def proc = command.execute([], basedir) + proc.waitFor() + proc.err.text +} + +String out = exec("java -jar ${jarfile}") +assert out.contains('Hello Embedded World!'), + 'Using -jar my.jar should load dependencies from separate jar and use the application.properties from the jar\n' + out + +out = exec("java -cp ${jarfile} org.springframework.boot.loader.PropertiesLauncher") +assert out.contains('Hello Embedded World!'), + 'Using -cp my.jar with PropertiesLauncher should load dependencies from separate jar and use the application.properties from the jar\n' + out 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 083f639214f..01253279ac3 100755 --- 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,12 +21,10 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.jar.Manifest; @@ -469,10 +467,10 @@ public class PropertiesLauncher extends Launcher { debug("Adding classpath entries from archive " + archive.getUrl() + root); lib.add(archive); } - Archive nested = getNestedArchive(root); - if (nested != null) { + List nestedArchives = getNestedArchives(root); + if (nestedArchives != null) { debug("Adding classpath entries from nested " + root); - lib.add(nested); + lib.addAll(nestedArchives); } return lib; } @@ -490,19 +488,24 @@ public class PropertiesLauncher extends Launcher { return null; } - private Archive getNestedArchive(String root) throws Exception { + private List getNestedArchives(String root) throws Exception { if (root.startsWith("/") || this.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 (this.parent.getNestedArchives(filter).isEmpty()) { - return null; + Archive parent = this.parent; + if (root.startsWith("jar:file:") && root.contains("!")) { + int index = root.indexOf("!"); + String file = root.substring("jar:file:".length(), index); + parent = new JarFileArchive(new File(file)); + root = root.substring(index + 1, root.length()); + while (root.startsWith("/")) { + root = root.substring(1); + } } - // 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(this.parent, filter); + EntryFilter filter = new PrefixMatchingArchiveFilter(root); + return parent.getNestedArchives(filter); } private void addNestedEntries(List lib) { @@ -626,47 +629,4 @@ public class PropertiesLauncher extends Launcher { } - /** - * Decorator to apply an {@link Archive.EntryFilter} to an existing {@link Archive}. - */ - private static class FilteredArchive implements Archive { - - private final Archive parent; - - private final EntryFilter filter; - - FilteredArchive(Archive parent, EntryFilter filter) { - this.parent = parent; - this.filter = filter; - } - - @Override - public URL getUrl() throws MalformedURLException { - return this.parent.getUrl(); - } - - @Override - public Manifest getManifest() throws IOException { - return this.parent.getManifest(); - } - - @Override - public Iterator iterator() { - throw new UnsupportedOperationException(); - } - - @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); - } - }); - } - - } - }