diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index f336f082fec..a6a7624bb94 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -41,6 +41,7 @@ import org.gradle.api.tasks.bundling.War; * * @author Andy Wilkinson * @author Phillip Webb + * @author Scott Frederick * @since 2.0.0 */ public class BootWar extends War implements BootArchive { @@ -55,6 +56,8 @@ public class BootWar extends War implements BootArchive { private static final String LAYERS_INDEX = "WEB-INF/layers.idx"; + private static final String CLASSPATH_INDEX = "WEB-INF/classpath.idx"; + private final BootArchiveSupport support; private final Property mainClass; @@ -91,8 +94,8 @@ public class BootWar extends War implements BootArchive { @Override public void copy() { - this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null, - (isLayeredDisabled()) ? null : LAYERS_INDEX); + this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, + CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX); super.copy(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index fb23adb6bde..8241cbc68a5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -500,9 +500,7 @@ abstract class AbstractBootArchiveTests { expected.add("- \"application\":"); Set applicationContents = new TreeSet<>(); applicationContents.add(" - \"" + this.classesPath + "\""); - if (archiveHasClasspathIndex()) { - applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); - } + applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); applicationContents.add(" - \"" + this.indexPath + "layers.idx\""); applicationContents.add(" - \"META-INF/\""); expected.addAll(applicationContents); @@ -551,9 +549,7 @@ abstract class AbstractBootArchiveTests { Set applicationContents = new TreeSet<>(); applicationContents.add(" - \"" + this.classesPath + "application.properties\""); applicationContents.add(" - \"" + this.classesPath + "com/\""); - if (archiveHasClasspathIndex()) { - applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); - } + applicationContents.add(" - \"" + this.indexPath + "classpath.idx\""); applicationContents.add(" - \"" + this.indexPath + "layers.idx\""); applicationContents.add(" - \"META-INF/\""); applicationContents.add(" - \"org/\""); @@ -634,12 +630,14 @@ abstract class AbstractBootArchiveTests { return getTask().getArchiveFile().get().getAsFile(); } - abstract void applyLayered(Action action); - - boolean archiveHasClasspathIndex() { - return true; + File createPopulatedJar() throws IOException { + addContent(); + executeTask(); + return getTask().getArchiveFile().get().getAsFile(); } + abstract void applyLayered(Action action); + @SuppressWarnings("unchecked") void addContent() throws IOException { this.task.getMainClass().set("com.example.Main"); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java index 5a99b1fa6a4..29df4515637 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootJarTests.java @@ -103,12 +103,6 @@ class BootJarTests extends AbstractBootArchiveTests { } } - private File createPopulatedJar() throws IOException { - addContent(); - executeTask(); - return getTask().getArchiveFile().get().getAsFile(); - } - @Override void applyLayered(Action action) { getTask().layered(action); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java index 8c29f55e152..fe609b26b0b 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.gradle.junit.GradleCompatibility; * Integration tests for {@link BootWar}. * * @author Andy Wilkinson + * @author Scott Frederick */ @GradleCompatibility(configurationCache = true) class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { @@ -37,7 +38,7 @@ class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests { @Override String[] getExpectedApplicationLayerContents(String... additionalFiles) { Set contents = new TreeSet<>(Arrays.asList(additionalFiles)); - contents.addAll(Arrays.asList("WEB-INF/layers.idx", "META-INF/")); + contents.addAll(Arrays.asList("WEB-INF/classpath.idx", "WEB-INF/layers.idx", "META-INF/")); return contents.toArray(new String[0]); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java index 6ffff4a4360..ba240b8572f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/BootWarTests.java @@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link BootWar}. * * @author Andy Wilkinson + * @author Scott Frederick */ class BootWarTests extends AbstractBootArchiveTests { @@ -109,6 +110,28 @@ class BootWarTests extends AbstractBootArchiveTests { .containsSubsequence("WEB-INF/lib/library.jar", "WEB-INF/lib-provided/provided-library.jar"); } + @Test + void whenWarIsLayeredClasspathIndexPointsToLayeredLibs() throws IOException { + try (JarFile jarFile = new JarFile(createLayeredJar())) { + assertThat(entryLines(jarFile, "WEB-INF/classpath.idx")).containsExactly( + "- \"WEB-INF/lib/first-library.jar\"", "- \"WEB-INF/lib/second-library.jar\"", + "- \"WEB-INF/lib/third-library-SNAPSHOT.jar\"", "- \"WEB-INF/lib/first-project-library.jar\"", + "- \"WEB-INF/lib/second-project-library-SNAPSHOT.jar\""); + } + } + + @Test + void classpathIndexPointsToWebInfLibs() throws IOException { + try (JarFile jarFile = new JarFile(createPopulatedJar())) { + assertThat(jarFile.getManifest().getMainAttributes().getValue("Spring-Boot-Classpath-Index")) + .isEqualTo("WEB-INF/classpath.idx"); + assertThat(entryLines(jarFile, "WEB-INF/classpath.idx")).containsExactly( + "- \"WEB-INF/lib/first-library.jar\"", "- \"WEB-INF/lib/second-library.jar\"", + "- \"WEB-INF/lib/third-library-SNAPSHOT.jar\"", "- \"WEB-INF/lib/first-project-library.jar\"", + "- \"WEB-INF/lib/second-project-library-SNAPSHOT.jar\""); + } + } + @Override protected void executeTask() { getTask().copy(); @@ -124,9 +147,4 @@ class BootWarTests extends AbstractBootArchiveTests { getTask().layered(action); } - @Override - boolean archiveHasClasspathIndex() { - return false; - } - } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java index 87e61c60a97..61586d3d1c5 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Layouts.java @@ -161,6 +161,11 @@ public final class Layouts { return "WEB-INF/classes/"; } + @Override + public String getClasspathIndexFileLocation() { + return "WEB-INF/classpath.idx"; + } + @Override public String getLayersIndexFileLocation() { return "WEB-INF/layers.idx"; diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java index 07fad13e0b8..a42e80526d7 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -21,9 +21,11 @@ import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.jar.Attributes; import java.util.jar.Manifest; import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.archive.ExplodedArchive; /** * Base class for executable archive {@link Launcher}s. @@ -31,6 +33,7 @@ import org.springframework.boot.loader.archive.Archive; * @author Phillip Webb * @author Andy Wilkinson * @author Madhura Bhave + * @author Scott Frederick * @since 1.0.0 */ public abstract class ExecutableArchiveLauncher extends Launcher { @@ -39,6 +42,8 @@ public abstract class ExecutableArchiveLauncher extends Launcher { protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index"; + protected static final String DEFAULT_CLASSPATH_INDEX_FILE_NAME = "classpath.idx"; + private final Archive archive; private final ClassPathIndexFile classPathIndex; @@ -64,9 +69,21 @@ public abstract class ExecutableArchiveLauncher extends Launcher { } protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException { + // Only needed for exploded archives, regular ones already have a defined order + if (archive instanceof ExplodedArchive) { + String location = getClassPathIndexFileLocation(archive); + return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location); + } return null; } + private String getClassPathIndexFileLocation(Archive archive) throws IOException { + Manifest manifest = archive.getManifest(); + Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; + String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null; + return (location != null) ? location : getArchiveEntryPathPrefix() + DEFAULT_CLASSPATH_INDEX_FILE_NAME; + } + @Override protected String getMainClass() throws Exception { Manifest manifest = this.archive.getManifest(); @@ -133,7 +150,10 @@ public abstract class ExecutableArchiveLauncher extends Launcher { * @since 2.3.0 */ protected boolean isSearchCandidate(Archive.Entry entry) { - return true; + if (getArchiveEntryPathPrefix() == null) { + return true; + } + return entry.getName().startsWith(getArchiveEntryPathPrefix()); } /** @@ -166,6 +186,14 @@ public abstract class ExecutableArchiveLauncher extends Launcher { protected void postProcessClassPathArchives(List archives) throws Exception { } + /** + * Return the path prefix for entries in the archive. + * @return the path prefix + */ + protected String getArchiveEntryPathPrefix() { + return null; + } + @Override protected boolean isExploded() { return this.archive.isExploded(); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java index c10b28e6ea5..2c86b3d41f4 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,13 +16,8 @@ package org.springframework.boot.loader; -import java.io.IOException; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.archive.Archive.EntryFilter; -import org.springframework.boot.loader.archive.ExplodedArchive; /** * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are @@ -32,12 +27,11 @@ import org.springframework.boot.loader.archive.ExplodedArchive; * @author Phillip Webb * @author Andy Wilkinson * @author Madhura Bhave + * @author Scott Frederick * @since 1.0.0 */ public class JarLauncher extends ExecutableArchiveLauncher { - private static final String DEFAULT_CLASSPATH_INDEX_LOCATION = "BOOT-INF/classpath.idx"; - static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { if (entry.isDirectory()) { return entry.getName().equals("BOOT-INF/classes/"); @@ -52,38 +46,21 @@ public class JarLauncher extends ExecutableArchiveLauncher { super(archive); } - @Override - protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException { - // Only needed for exploded archives, regular ones already have a defined order - if (archive instanceof ExplodedArchive) { - String location = getClassPathIndexFileLocation(archive); - return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location); - } - return super.getClassPathIndex(archive); - } - - private String getClassPathIndexFileLocation(Archive archive) throws IOException { - Manifest manifest = archive.getManifest(); - Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; - String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null; - return (location != null) ? location : DEFAULT_CLASSPATH_INDEX_LOCATION; - } - @Override protected boolean isPostProcessingClassPathArchives() { return false; } - @Override - protected boolean isSearchCandidate(Archive.Entry entry) { - return entry.getName().startsWith("BOOT-INF/"); - } - @Override protected boolean isNestedArchive(Archive.Entry entry) { return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); } + @Override + protected String getArchiveEntryPathPrefix() { + return "BOOT-INF/"; + } + public static void main(String[] args) throws Exception { new JarLauncher().launch(args); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java index 6f7d18ff5de..81e0a744144 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -17,7 +17,6 @@ package org.springframework.boot.loader; import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.Archive.Entry; /** * {@link Launcher} for WAR based archives. This launcher for standard WAR archives. @@ -26,6 +25,7 @@ import org.springframework.boot.loader.archive.Archive.Entry; * * @author Phillip Webb * @author Andy Wilkinson + * @author Scott Frederick * @since 1.0.0 */ public class WarLauncher extends ExecutableArchiveLauncher { @@ -42,11 +42,6 @@ public class WarLauncher extends ExecutableArchiveLauncher { return false; } - @Override - protected boolean isSearchCandidate(Entry entry) { - return entry.getName().startsWith("WEB-INF/"); - } - @Override public boolean isNestedArchive(Archive.Entry entry) { if (entry.isDirectory()) { @@ -55,6 +50,11 @@ public class WarLauncher extends ExecutableArchiveLauncher { return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/"); } + @Override + protected String getArchiveEntryPathPrefix() { + return "WEB-INF/"; + } + public static void main(String[] args) throws Exception { new WarLauncher().launch(args); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java index a461ba1f1dd..48d7340ee38 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -47,6 +47,7 @@ import org.springframework.util.FileCopyUtils; * * @author Andy Wilkinson * @author Madhura Bhave + * @author Scott Frederick */ public abstract class AbstractExecutableArchiveLauncherTests { @@ -80,9 +81,9 @@ public abstract class AbstractExecutableArchiveLauncherTests { if (indexed) { jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classpath.idx")); Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); - writer.write("- \"BOOT-INF/lib/foo.jar\"\n"); - writer.write("- \"BOOT-INF/lib/bar.jar\"\n"); - writer.write("- \"BOOT-INF/lib/baz.jar\"\n"); + writer.write("- \"" + entryPrefix + "/lib/foo.jar\"\n"); + writer.write("- \"" + entryPrefix + "/lib/bar.jar\"\n"); + writer.write("- \"" + entryPrefix + "/lib/baz.jar\"\n"); writer.flush(); jarOutputStream.closeEntry(); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java index b0d5539ddfe..aef78cfa53d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/WarLauncherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -18,7 +18,11 @@ package org.springframework.boot.loader; import java.io.File; import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import org.junit.jupiter.api.Test; @@ -33,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link WarLauncher}. * * @author Andy Wilkinson + * @author Scott Frederick */ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { @@ -66,6 +71,29 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { } } + @Test + void explodedWarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception { + File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, Collections.emptyList())); + WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); + Iterator archives = launcher.getClassPathArchivesIterator(); + URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); + URL[] urls = classLoader.getURLs(); + assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot)); + } + + @Test + void warFilesPresentInWebInfLibsAndNotInClasspathIndexShouldBeAddedAfterWebInfClasses() throws Exception { + ArrayList extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar")); + File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, extraLibs)); + WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); + Iterator archives = launcher.getClassPathArchivesIterator(); + URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); + URL[] urls = classLoader.getURLs(); + List expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot); + URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new); + assertThat(urls).containsExactly(expectedFileUrls); + } + protected final URL[] getExpectedFileUrls(File explodedRoot) { return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new); } @@ -79,4 +107,15 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { return expected; } + protected final List getExpectedFilesWithExtraLibs(File parent) { + List expected = new ArrayList<>(); + expected.add(new File(parent, "WEB-INF/classes")); + expected.add(new File(parent, "WEB-INF/lib/extra-1.jar")); + expected.add(new File(parent, "WEB-INF/lib/extra-2.jar")); + expected.add(new File(parent, "WEB-INF/lib/foo.jar")); + expected.add(new File(parent, "WEB-INF/lib/bar.jar")); + expected.add(new File(parent, "WEB-INF/lib/baz.jar")); + return expected; + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AbstractArchiveIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AbstractArchiveIntegrationTests.java index e606d4da0e0..44ed3dc42b3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AbstractArchiveIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/AbstractArchiveIntegrationTests.java @@ -75,17 +75,20 @@ abstract class AbstractArchiveIntegrationTests { return Collections.emptyMap(); } Map> index = new LinkedHashMap<>(); + String layerPrefix = "- "; + String entryPrefix = " - "; ZipEntry indexEntry = jarFile.getEntry(getLayersIndexLocation()); try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) { String line = reader.readLine(); String layer = null; while (line != null) { - if (line.startsWith("- ")) { - layer = line.substring(3, line.length() - 2); + if (line.startsWith(layerPrefix)) { + layer = line.substring(layerPrefix.length() + 1, line.length() - 2); index.put(layer, new ArrayList<>()); } - else if (line.startsWith(" - ")) { - index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1)); + else if (line.startsWith(entryPrefix)) { + index.computeIfAbsent(layer, (key) -> new ArrayList<>()) + .add(line.substring(entryPrefix.length() + 1, line.length() - 1)); } line = reader.readLine(); } @@ -97,6 +100,22 @@ abstract class AbstractArchiveIntegrationTests { return null; } + protected List readClasspathIndex(JarFile jarFile, String location) throws IOException { + List index = new ArrayList<>(); + String entryPrefix = "- "; + ZipEntry indexEntry = jarFile.getEntry(location); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) { + String line = reader.readLine(); + while (line != null) { + if (line.startsWith(entryPrefix)) { + index.add(line.substring(entryPrefix.length() + 1, line.length() - 1)); + } + line = reader.readLine(); + } + } + return index; + } + static final class JarAssert extends AbstractAssert { private JarAssert(File actual) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java index 9232fe6fbd8..ede4d736aa1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java @@ -372,6 +372,10 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests { assertThat(jar(repackaged)).manifest( (manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx")); assertThat(jar(repackaged)).hasEntryWithName("BOOT-INF/classpath.idx"); + try (JarFile jarFile = new JarFile(repackaged)) { + List index = readClasspathIndex(jarFile, "BOOT-INF/classpath.idx"); + assertThat(index).allMatch((entry) -> entry.startsWith("BOOT-INF/lib/")); + } }); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java index 6efc9810fdb..ec55f143c6d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/WarIntegrationTests.java @@ -211,12 +211,17 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests { } @TestTemplate - void repackagedWarDoesNotContainClasspathIndex(MavenBuild mavenBuild) { + void repackagedWarContainsClasspathIndex(MavenBuild mavenBuild) { mavenBuild.project("war").execute((project) -> { File repackaged = new File(project, "target/war-0.0.1.BUILD-SNAPSHOT.war"); - assertThat(jar(repackaged)) - .manifest((manifest) -> manifest.doesNotHaveAttribute("Spring-Boot-Classpath-Index")); - assertThat(jar(repackaged)).doesNotHaveEntryWithName("BOOT-INF/classpath.idx"); + assertThat(jar(repackaged)).manifest( + (manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "WEB-INF/classpath.idx")); + assertThat(jar(repackaged)).hasEntryWithName("WEB-INF/classpath.idx"); + try (JarFile jarFile = new JarFile(repackaged)) { + List index = readClasspathIndex(jarFile, "WEB-INF/classpath.idx"); + assertThat(index).allMatch( + (entry) -> entry.startsWith("WEB-INF/lib/") || entry.startsWith("WEB-INF/lib-provided/")); + } }); }