From 3c7361fb3e92aa4a5850a6e6dd33dbacf8b47f7f Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Mon, 30 Dec 2013 16:14:10 +0000 Subject: [PATCH] Support for wildcard paths in PropertiesLauncher --- .../spring-boot-loader/README.md | 2 +- .../boot/loader/PropertiesLauncher.java | 28 +++- .../boot/loader/OutputCapture.java | 127 ++++++++++++++++++ .../boot/loader/PropertiesLauncherTests.java | 39 +++++- .../src/test/resources/jars/app.jar | Bin 0 -> 1146 bytes 5 files changed, 191 insertions(+), 5 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/OutputCapture.java create mode 100644 spring-boot-tools/spring-boot-loader/src/test/resources/jars/app.jar diff --git a/spring-boot-tools/spring-boot-loader/README.md b/spring-boot-tools/spring-boot-loader/README.md index f245eb5d6f9..e141bf2e572 100644 --- a/spring-boot-tools/spring-boot-loader/README.md +++ b/spring-boot-tools/spring-boot-loader/README.md @@ -11,7 +11,7 @@ opposed to explicitly on the classpath). In the case of the those locations if you want more. The `PropertiesLauncher` looks in `lib/` by default, but you can add additional locations by setting an environment variable `LOADER_PATH`or `loader.path` in -`application.properties` (colon-separated list of directories). +`application.properties` (colon-separated list of directories or archives). > **Note:** The quickest way to build a compatible archive is to use the > [spring-boot-maven-plugin](../spring-boot-maven-plugin) or 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 accead37edd..d4a3639d72e 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 @@ -343,6 +343,12 @@ public class PropertiesLauncher extends Launcher { Archive archive = new ExplodedArchive(file); lib.add(archive); } + Archive archive = getArchive(file); + if (archive != null) { + this.logger.info("Adding classpath entries from nested " + archive.getUrl() + + root); + lib.add(archive); + } Archive nested = getNestedArchive(root); if (nested != null) { this.logger.info("Adding classpath entries from nested " + nested.getUrl() @@ -352,6 +358,14 @@ public class PropertiesLauncher extends Launcher { return lib; } + private Archive getArchive(File file) throws IOException { + String name = file.getName().toLowerCase(); + if (name.endsWith(".jar") || name.endsWith(".zip")) { + return new JarFileArchive(file); + } + return null; + } + private Archive getNestedArchive(final String root) throws Exception { Archive parent = createArchive(); if (root.startsWith("/") || parent.getUrl().equals(this.home.toURI().toURL())) { @@ -401,9 +415,17 @@ public class PropertiesLauncher extends Launcher { private String cleanupPath(String path) { path = path.trim(); - // Always a directory - if (!path.endsWith("/")) { - path = path + "/"; + if (path.toLowerCase().endsWith(".jar") || path.toLowerCase().endsWith(".zip")) { + return path; + } + if (path.endsWith("/*")) { + path = path.substring(0, path.length() - 1); + } + else { + // It's a directory + if (!path.endsWith("/")) { + path = path + "/"; + } } // No need for current dir path if (path.startsWith("./")) { diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/OutputCapture.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/OutputCapture.java new file mode 100644 index 00000000000..8e39c0ce65e --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/OutputCapture.java @@ -0,0 +1,127 @@ +/* + * 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.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * Capture output from System.out and System.err. + * + * @author Phillip Webb + */ +public class OutputCapture implements TestRule { + + private CaptureOutputStream captureOut; + + private CaptureOutputStream captureErr; + + private ByteArrayOutputStream copy; + + @Override + public Statement apply(final Statement base, Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + captureOutput(); + try { + base.evaluate(); + } + finally { + releaseOutput(); + } + } + }; + } + + protected void captureOutput() { + this.copy = new ByteArrayOutputStream(); + this.captureOut = new CaptureOutputStream(System.out, this.copy); + this.captureErr = new CaptureOutputStream(System.err, this.copy); + System.setOut(new PrintStream(this.captureOut)); + System.setErr(new PrintStream(this.captureErr)); + } + + protected void releaseOutput() { + System.setOut(this.captureOut.getOriginal()); + System.setErr(this.captureErr.getOriginal()); + this.copy = null; + } + + public void flush() { + try { + this.captureOut.flush(); + this.captureErr.flush(); + } + catch (IOException ex) { + // ignore + } + } + + @Override + public String toString() { + flush(); + return this.copy.toString(); + } + + private static class CaptureOutputStream extends OutputStream { + + private final PrintStream original; + + private final OutputStream copy; + + public CaptureOutputStream(PrintStream original, OutputStream copy) { + this.original = original; + this.copy = copy; + } + + @Override + public void write(int b) throws IOException { + this.copy.write(b); + this.original.write(b); + this.original.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.copy.write(b, off, len); + this.original.write(b, off, len); + } + + public PrintStream getOriginal() { + return this.original; + } + + @Override + public void flush() throws IOException { + this.copy.flush(); + this.original.flush(); + } + } + +} 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 d3f68360f17..11228f0f8f3 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 @@ -21,10 +21,12 @@ import java.io.IOException; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.springframework.test.util.ReflectionTestUtils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests for {@link PropertiesLauncher}. @@ -33,6 +35,9 @@ import static org.junit.Assert.assertEquals; */ public class PropertiesLauncherTests { + @Rule + public OutputCapture output = new OutputCapture(); + @Before public void setup() throws IOException { System.setProperty("loader.home", @@ -65,13 +70,34 @@ public class PropertiesLauncherTests { @Test public void testUserSpecifiedConfigName() throws Exception { - System.setProperty("loader.config.name", "foo"); PropertiesLauncher launcher = new PropertiesLauncher(); assertEquals("my.Application", launcher.getMainClass()); assertEquals("[etc/]", ReflectionTestUtils.getField(launcher, "paths").toString()); } + @Test + public void testUserSpecifiedPath() throws Exception { + System.setProperty("loader.path", "jars/*"); + System.setProperty("loader.main", "demo.Application"); + PropertiesLauncher launcher = new PropertiesLauncher(); + assertEquals("[jars/]", ReflectionTestUtils.getField(launcher, "paths") + .toString()); + launcher.launch(new String[0]); + waitFor("Hello World"); + } + + @Test + public void testUserSpecifiedJarPath() throws Exception { + System.setProperty("loader.path", "jars/app.jar"); + System.setProperty("loader.main", "demo.Application"); + PropertiesLauncher launcher = new PropertiesLauncher(); + assertEquals("[jars/app.jar]", ReflectionTestUtils.getField(launcher, "paths") + .toString()); + launcher.launch(new String[0]); + waitFor("Hello World"); + } + @Test public void testUserSpecifiedConfigPathWins() throws Exception { @@ -95,4 +121,15 @@ public class PropertiesLauncherTests { assertEquals("demo.Application", System.getProperty("loader.main")); } + private void waitFor(String value) throws Exception { + int count = 0; + boolean timeout = false; + while (!timeout && count < 100) { + count++; + Thread.sleep(50L); + timeout = this.output.toString().contains(value); + } + assertTrue("Timed out waiting for (" + value + ")", timeout); + } + } diff --git a/spring-boot-tools/spring-boot-loader/src/test/resources/jars/app.jar b/spring-boot-tools/spring-boot-loader/src/test/resources/jars/app.jar new file mode 100644 index 0000000000000000000000000000000000000000..c7c485ae5dac49a6b115c4d9f747e960caf34286 GIT binary patch literal 1146 zcmWIWW@Zs#-~hr~HS?SqkN_tG3xls~h@-BjpPT-_Qw$8u3<2Kk93T};P-Ou)HH!dM zz%~0i`gyv!28ZbRx_$ONbK1vSSMMUPx31Q?Gv_x48C)@b@U%$J%U8$K_hRWP7S0(j zC67qY)W~3&{!H^nnwt1i@o=%}OP+~oS3FaFo%yNgV-X|RA^Db*SlxlvfH24*Twq6C zLGuMGP&6eqH(wv&)-z~I#9&Gs3kq^FlM_oa^YiqQa}tY-a|2?%nH)uI7q4FYdfKwg z`O9ost~qn>m2z~s*c_5@#o7O{)rk{t&TU#3Xun#HtLg>&m&J>AOQ$+97u%n+eSU85 z=Z`q!eZH`XjbQg-PZDH?OV*ZTgx=2CUyV#$V9v6XF?%-_jtI&R*s&z}79D9={O zFV=@-HvYHhT*=q%ql{&@`Tycx@)t%Y~~)krosGT>!sL){{@Zz-|&A9HhD5pOm?Sb*V6ZVj1wPt zh}N3>+<%>BmV6K70r%w}bLxQ3n+?PP-i%Bl45--!mKQ+T1r@;4KPdB{Yei1Ipv-~* zwm>FaD^k`0nZU(>JC!3$I0j@w(>%hxpj3|>8K6{;055?|uojFwfb4Kkv>}HFDB2Jp c2N)C>W`H6wz?+o~q>L2^&4KAaml?zZ03{AymjD0& literal 0 HcmV?d00001