From ade22c87ece92b760d85db8d2c9a0049433a9f49 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 11 Nov 2014 14:42:19 +0000 Subject: [PATCH] Update WarLauncher to remove the root of the war from the classpath Previously, WarLauncher included its root on the classpath. It also used a filtered version of its root archive to hide both the WEB-INF and META-INF directories. This meant that files in WEB-INF and META-INF could be found by the classloader (as they were on the classpath) but could not be read as the filtered archive was hiding them. This commit updates WarLauncher to remove the root of the war file from the classpath. It also removes the filtering of the archive, thereby allowing files in META-INF and WEB-INF to be accessed via the ServletContext. Closes gh-1792 --- .../loader/ExecutableArchiveLauncher.java | 5 + .../boot/loader/WarLauncher.java | 37 ++---- .../boot/loader/WarLauncherTests.java | 124 ++++++++++++++++++ 3 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index d1a4beb707..ad6f14cbbb 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -54,6 +54,11 @@ public abstract class ExecutableArchiveLauncher extends Launcher { this.javaAgentDetector = javaAgentDetector; } + ExecutableArchiveLauncher(Archive archive) { + this.javaAgentDetector = new InputArgumentsJavaAgentDetector(); + this.archive = archive; + } + protected final Archive getArchive() { return this.archive; } diff --git a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index cafda40b31..82a745aef7 100644 --- a/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -16,9 +16,6 @@ package org.springframework.boot.loader; -import java.io.IOException; -import java.util.List; - import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.util.AsciiBytes; @@ -28,13 +25,12 @@ import org.springframework.boot.loader.util.AsciiBytes; * classes are loaded from {@code WEB-INF/classes}. * * @author Phillip Webb + * @author Andy Wilkinson */ public class WarLauncher extends ExecutableArchiveLauncher { private static final AsciiBytes WEB_INF = new AsciiBytes("WEB-INF/"); - private static final AsciiBytes META_INF = new AsciiBytes("META-INF/"); - private static final AsciiBytes WEB_INF_CLASSES = WEB_INF.append("classes/"); private static final AsciiBytes WEB_INF_LIB = WEB_INF.append("lib/"); @@ -42,6 +38,14 @@ public class WarLauncher extends ExecutableArchiveLauncher { private static final AsciiBytes WEB_INF_LIB_PROVIDED = WEB_INF .append("lib-provided/"); + public WarLauncher() { + super(); + } + + WarLauncher(Archive archive) { + super(archive); + } + @Override public boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { @@ -53,29 +57,6 @@ public class WarLauncher extends ExecutableArchiveLauncher { } } - @Override - protected void postProcessClassPathArchives(List archives) throws Exception { - archives.add(0, getFilteredArchive()); - } - - /** - * Filter the specified WAR file to exclude elements that should not appear on the - * classpath. - * @return the filtered archive - * @throws IOException on error - */ - protected Archive getFilteredArchive() throws IOException { - return getArchive().getFilteredArchive(new Archive.EntryRenameFilter() { - @Override - public AsciiBytes apply(AsciiBytes entryName, Archive.Entry entry) { - if (entryName.startsWith(META_INF) || entryName.startsWith(WEB_INF)) { - return null; - } - return entryName; - } - }); - } - public static void main(String[] args) { new WarLauncher().launch(args); } diff --git a/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java new file mode 100644 index 0000000000..c5998c04a0 --- /dev/null +++ b/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2012-2014 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.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; + +import org.junit.Test; +import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.ExplodedArchive; +import org.springframework.boot.loader.archive.JarFileArchive; +import org.springframework.util.FileSystemUtils; + +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link WarLauncher} + * + * @author Andy Wilkinson + */ +public class WarLauncherTests { + + @Test + public void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() + throws Exception { + File warRoot = new File("target/exploded-war"); + FileSystemUtils.deleteRecursively(warRoot); + warRoot.mkdirs(); + File webInfClasses = new File(warRoot, "WEB-INF/classes"); + webInfClasses.mkdirs(); + File webInfLib = new File(warRoot, "WEB-INF/lib"); + webInfLib.mkdirs(); + File webInfLibFoo = new File(webInfLib, "foo.jar"); + new JarOutputStream(new FileOutputStream(webInfLibFoo)).close(); + + WarLauncher launcher = new WarLauncher(new ExplodedArchive(warRoot, true)); + List archives = launcher.getClassPathArchives(); + assertEquals(2, archives.size()); + + assertThat( + getUrls(archives), + hasItems(webInfClasses.toURI().toURL(), new URL("jar:" + + webInfLibFoo.toURI().toURL() + "!/"))); + } + + @Test + public void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() + throws Exception { + File warRoot = createWarArchive(); + + WarLauncher launcher = new WarLauncher(new JarFileArchive(warRoot)); + List archives = launcher.getClassPathArchives(); + assertEquals(2, archives.size()); + + assertThat( + getUrls(archives), + hasItems( + new URL("jar:" + warRoot.toURI().toURL() + "!/WEB-INF/classes!/"), + new URL("jar:" + warRoot.toURI().toURL() + + "!/WEB-INF/lib/foo.jar!/"))); + } + + private Set getUrls(List archives) throws MalformedURLException { + Set urls = new HashSet(archives.size()); + for (Archive archive : archives) { + urls.add(archive.getUrl()); + } + return urls; + } + + private File createWarArchive() throws IOException, FileNotFoundException { + File warRoot = new File("target/archive.war"); + warRoot.delete(); + + JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream( + warRoot)); + + jarOutputStream.putNextEntry(new JarEntry("WEB-INF/")); + jarOutputStream.putNextEntry(new JarEntry("WEB-INF/classes/")); + jarOutputStream.putNextEntry(new JarEntry("WEB-INF/lib/")); + + JarEntry webInfLibFoo = new JarEntry("WEB-INF/lib/foo.jar"); + webInfLibFoo.setMethod(ZipEntry.STORED); + ByteArrayOutputStream fooJarStream = new ByteArrayOutputStream(); + new JarOutputStream(fooJarStream).close(); + webInfLibFoo.setSize(fooJarStream.size()); + CRC32 crc32 = new CRC32(); + crc32.update(fooJarStream.toByteArray()); + webInfLibFoo.setCrc(crc32.getValue()); + jarOutputStream.putNextEntry(webInfLibFoo); + jarOutputStream.write(fooJarStream.toByteArray()); + + jarOutputStream.close(); + return warRoot; + } +}