Add classpath index support for exploded war archives
Update the Maven and Gradle packaging for war files so that a `classpath.idx` file is written into the archive that provides the original order of the classpath, as was previously done for jar files. The `WarLauncher` class will use this file when running as an exploded archive to ensure that the classpath order is the same as when running from the far war. Fixes gh-19875
This commit is contained in:
parent
8b5600fca8
commit
8f57f0babb
|
@ -41,6 +41,7 @@ import org.gradle.api.tasks.bundling.War;
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class BootWar extends War implements BootArchive {
|
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 LAYERS_INDEX = "WEB-INF/layers.idx";
|
||||||
|
|
||||||
|
private static final String CLASSPATH_INDEX = "WEB-INF/classpath.idx";
|
||||||
|
|
||||||
private final BootArchiveSupport support;
|
private final BootArchiveSupport support;
|
||||||
|
|
||||||
private final Property<String> mainClass;
|
private final Property<String> mainClass;
|
||||||
|
@ -91,8 +94,8 @@ public class BootWar extends War implements BootArchive {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void copy() {
|
public void copy() {
|
||||||
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null,
|
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
|
||||||
(isLayeredDisabled()) ? null : LAYERS_INDEX);
|
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX);
|
||||||
super.copy();
|
super.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -500,9 +500,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
||||||
expected.add("- \"application\":");
|
expected.add("- \"application\":");
|
||||||
Set<String> applicationContents = new TreeSet<>();
|
Set<String> applicationContents = new TreeSet<>();
|
||||||
applicationContents.add(" - \"" + this.classesPath + "\"");
|
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(" - \"" + this.indexPath + "layers.idx\"");
|
||||||
applicationContents.add(" - \"META-INF/\"");
|
applicationContents.add(" - \"META-INF/\"");
|
||||||
expected.addAll(applicationContents);
|
expected.addAll(applicationContents);
|
||||||
|
@ -551,9 +549,7 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
||||||
Set<String> applicationContents = new TreeSet<>();
|
Set<String> applicationContents = new TreeSet<>();
|
||||||
applicationContents.add(" - \"" + this.classesPath + "application.properties\"");
|
applicationContents.add(" - \"" + this.classesPath + "application.properties\"");
|
||||||
applicationContents.add(" - \"" + this.classesPath + "com/\"");
|
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(" - \"" + this.indexPath + "layers.idx\"");
|
||||||
applicationContents.add(" - \"META-INF/\"");
|
applicationContents.add(" - \"META-INF/\"");
|
||||||
applicationContents.add(" - \"org/\"");
|
applicationContents.add(" - \"org/\"");
|
||||||
|
@ -634,12 +630,14 @@ abstract class AbstractBootArchiveTests<T extends Jar & BootArchive> {
|
||||||
return getTask().getArchiveFile().get().getAsFile();
|
return getTask().getArchiveFile().get().getAsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void applyLayered(Action<LayeredSpec> action);
|
File createPopulatedJar() throws IOException {
|
||||||
|
addContent();
|
||||||
boolean archiveHasClasspathIndex() {
|
executeTask();
|
||||||
return true;
|
return getTask().getArchiveFile().get().getAsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract void applyLayered(Action<LayeredSpec> action);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
void addContent() throws IOException {
|
void addContent() throws IOException {
|
||||||
this.task.getMainClass().set("com.example.Main");
|
this.task.getMainClass().set("com.example.Main");
|
||||||
|
|
|
@ -103,12 +103,6 @@ class BootJarTests extends AbstractBootArchiveTests<BootJar> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private File createPopulatedJar() throws IOException {
|
|
||||||
addContent();
|
|
||||||
executeTask();
|
|
||||||
return getTask().getArchiveFile().get().getAsFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void applyLayered(Action<LayeredSpec> action) {
|
void applyLayered(Action<LayeredSpec> action) {
|
||||||
getTask().layered(action);
|
getTask().layered(action);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.springframework.boot.gradle.junit.GradleCompatibility;
|
||||||
* Integration tests for {@link BootWar}.
|
* Integration tests for {@link BootWar}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
@GradleCompatibility(configurationCache = true)
|
@GradleCompatibility(configurationCache = true)
|
||||||
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||||
|
@ -37,7 +38,7 @@ class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
|
||||||
@Override
|
@Override
|
||||||
String[] getExpectedApplicationLayerContents(String... additionalFiles) {
|
String[] getExpectedApplicationLayerContents(String... additionalFiles) {
|
||||||
Set<String> contents = new TreeSet<>(Arrays.asList(additionalFiles));
|
Set<String> 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]);
|
return contents.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
* Tests for {@link BootWar}.
|
* Tests for {@link BootWar}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
||||||
|
|
||||||
|
@ -109,6 +110,28 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
||||||
.containsSubsequence("WEB-INF/lib/library.jar", "WEB-INF/lib-provided/provided-library.jar");
|
.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
|
@Override
|
||||||
protected void executeTask() {
|
protected void executeTask() {
|
||||||
getTask().copy();
|
getTask().copy();
|
||||||
|
@ -124,9 +147,4 @@ class BootWarTests extends AbstractBootArchiveTests<BootWar> {
|
||||||
getTask().layered(action);
|
getTask().layered(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean archiveHasClasspathIndex() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,11 @@ public final class Layouts {
|
||||||
return "WEB-INF/classes/";
|
return "WEB-INF/classes/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getClasspathIndexFileLocation() {
|
||||||
|
return "WEB-INF/classpath.idx";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getLayersIndexFileLocation() {
|
public String getLayersIndexFileLocation() {
|
||||||
return "WEB-INF/layers.idx";
|
return "WEB-INF/layers.idx";
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
import java.util.jar.Manifest;
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
import org.springframework.boot.loader.archive.Archive;
|
import org.springframework.boot.loader.archive.Archive;
|
||||||
|
import org.springframework.boot.loader.archive.ExplodedArchive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for executable archive {@link Launcher}s.
|
* Base class for executable archive {@link Launcher}s.
|
||||||
|
@ -31,6 +33,7 @@ import org.springframework.boot.loader.archive.Archive;
|
||||||
* @author Phillip Webb
|
* @author Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public abstract class ExecutableArchiveLauncher extends Launcher {
|
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 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 Archive archive;
|
||||||
|
|
||||||
private final ClassPathIndexFile classPathIndex;
|
private final ClassPathIndexFile classPathIndex;
|
||||||
|
@ -64,9 +69,21 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException {
|
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;
|
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
|
@Override
|
||||||
protected String getMainClass() throws Exception {
|
protected String getMainClass() throws Exception {
|
||||||
Manifest manifest = this.archive.getManifest();
|
Manifest manifest = this.archive.getManifest();
|
||||||
|
@ -133,7 +150,10 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||||
* @since 2.3.0
|
* @since 2.3.0
|
||||||
*/
|
*/
|
||||||
protected boolean isSearchCandidate(Archive.Entry entry) {
|
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<Archive> archives) throws Exception {
|
protected void postProcessClassPathArchives(List<Archive> archives) throws Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the path prefix for entries in the archive.
|
||||||
|
* @return the path prefix
|
||||||
|
*/
|
||||||
|
protected String getArchiveEntryPathPrefix() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isExploded() {
|
protected boolean isExploded() {
|
||||||
return this.archive.isExploded();
|
return this.archive.isExploded();
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,13 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.boot.loader;
|
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;
|
||||||
import org.springframework.boot.loader.archive.Archive.EntryFilter;
|
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
|
* {@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 Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class JarLauncher extends ExecutableArchiveLauncher {
|
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) -> {
|
static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
return entry.getName().equals("BOOT-INF/classes/");
|
return entry.getName().equals("BOOT-INF/classes/");
|
||||||
|
@ -52,38 +46,21 @@ public class JarLauncher extends ExecutableArchiveLauncher {
|
||||||
super(archive);
|
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
|
@Override
|
||||||
protected boolean isPostProcessingClassPathArchives() {
|
protected boolean isPostProcessingClassPathArchives() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isSearchCandidate(Archive.Entry entry) {
|
|
||||||
return entry.getName().startsWith("BOOT-INF/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean isNestedArchive(Archive.Entry entry) {
|
protected boolean isNestedArchive(Archive.Entry entry) {
|
||||||
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
|
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getArchiveEntryPathPrefix() {
|
||||||
|
return "BOOT-INF/";
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
new JarLauncher().launch(args);
|
new JarLauncher().launch(args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.boot.loader;
|
package org.springframework.boot.loader;
|
||||||
|
|
||||||
import org.springframework.boot.loader.archive.Archive;
|
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.
|
* {@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 Phillip Webb
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public class WarLauncher extends ExecutableArchiveLauncher {
|
public class WarLauncher extends ExecutableArchiveLauncher {
|
||||||
|
@ -42,11 +42,6 @@ public class WarLauncher extends ExecutableArchiveLauncher {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isSearchCandidate(Entry entry) {
|
|
||||||
return entry.getName().startsWith("WEB-INF/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNestedArchive(Archive.Entry entry) {
|
public boolean isNestedArchive(Archive.Entry entry) {
|
||||||
if (entry.isDirectory()) {
|
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/");
|
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 {
|
public static void main(String[] args) throws Exception {
|
||||||
new WarLauncher().launch(args);
|
new WarLauncher().launch(args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 Andy Wilkinson
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractExecutableArchiveLauncherTests {
|
public abstract class AbstractExecutableArchiveLauncherTests {
|
||||||
|
|
||||||
|
@ -80,9 +81,9 @@ public abstract class AbstractExecutableArchiveLauncherTests {
|
||||||
if (indexed) {
|
if (indexed) {
|
||||||
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classpath.idx"));
|
jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classpath.idx"));
|
||||||
Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8);
|
Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8);
|
||||||
writer.write("- \"BOOT-INF/lib/foo.jar\"\n");
|
writer.write("- \"" + entryPrefix + "/lib/foo.jar\"\n");
|
||||||
writer.write("- \"BOOT-INF/lib/bar.jar\"\n");
|
writer.write("- \"" + entryPrefix + "/lib/bar.jar\"\n");
|
||||||
writer.write("- \"BOOT-INF/lib/baz.jar\"\n");
|
writer.write("- \"" + entryPrefix + "/lib/baz.jar\"\n");
|
||||||
writer.flush();
|
writer.flush();
|
||||||
jarOutputStream.closeEntry();
|
jarOutputStream.closeEntry();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -33,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
* Tests for {@link WarLauncher}.
|
* Tests for {@link WarLauncher}.
|
||||||
*
|
*
|
||||||
* @author Andy Wilkinson
|
* @author Andy Wilkinson
|
||||||
|
* @author Scott Frederick
|
||||||
*/
|
*/
|
||||||
class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||||
|
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||||
|
URL[] urls = classLoader.getURLs();
|
||||||
|
assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void warFilesPresentInWebInfLibsAndNotInClasspathIndexShouldBeAddedAfterWebInfClasses() throws Exception {
|
||||||
|
ArrayList<String> 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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||||
|
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||||
|
URL[] urls = classLoader.getURLs();
|
||||||
|
List<File> expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot);
|
||||||
|
URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new);
|
||||||
|
assertThat(urls).containsExactly(expectedFileUrls);
|
||||||
|
}
|
||||||
|
|
||||||
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
protected final URL[] getExpectedFileUrls(File explodedRoot) {
|
||||||
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new);
|
||||||
}
|
}
|
||||||
|
@ -79,4 +107,15 @@ class WarLauncherTests extends AbstractExecutableArchiveLauncherTests {
|
||||||
return expected;
|
return expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected final List<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||||
|
List<File> 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,17 +75,20 @@ abstract class AbstractArchiveIntegrationTests {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
Map<String, List<String>> index = new LinkedHashMap<>();
|
Map<String, List<String>> index = new LinkedHashMap<>();
|
||||||
|
String layerPrefix = "- ";
|
||||||
|
String entryPrefix = " - ";
|
||||||
ZipEntry indexEntry = jarFile.getEntry(getLayersIndexLocation());
|
ZipEntry indexEntry = jarFile.getEntry(getLayersIndexLocation());
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(jarFile.getInputStream(indexEntry)))) {
|
||||||
String line = reader.readLine();
|
String line = reader.readLine();
|
||||||
String layer = null;
|
String layer = null;
|
||||||
while (line != null) {
|
while (line != null) {
|
||||||
if (line.startsWith("- ")) {
|
if (line.startsWith(layerPrefix)) {
|
||||||
layer = line.substring(3, line.length() - 2);
|
layer = line.substring(layerPrefix.length() + 1, line.length() - 2);
|
||||||
index.put(layer, new ArrayList<>());
|
index.put(layer, new ArrayList<>());
|
||||||
}
|
}
|
||||||
else if (line.startsWith(" - ")) {
|
else if (line.startsWith(entryPrefix)) {
|
||||||
index.computeIfAbsent(layer, (key) -> new ArrayList<>()).add(line.substring(5, line.length() - 1));
|
index.computeIfAbsent(layer, (key) -> new ArrayList<>())
|
||||||
|
.add(line.substring(entryPrefix.length() + 1, line.length() - 1));
|
||||||
}
|
}
|
||||||
line = reader.readLine();
|
line = reader.readLine();
|
||||||
}
|
}
|
||||||
|
@ -97,6 +100,22 @@ abstract class AbstractArchiveIntegrationTests {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<String> readClasspathIndex(JarFile jarFile, String location) throws IOException {
|
||||||
|
List<String> 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<JarAssert, File> {
|
static final class JarAssert extends AbstractAssert<JarAssert, File> {
|
||||||
|
|
||||||
private JarAssert(File actual) {
|
private JarAssert(File actual) {
|
||||||
|
|
|
@ -372,6 +372,10 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests {
|
||||||
assertThat(jar(repackaged)).manifest(
|
assertThat(jar(repackaged)).manifest(
|
||||||
(manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx"));
|
(manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "BOOT-INF/classpath.idx"));
|
||||||
assertThat(jar(repackaged)).hasEntryWithName("BOOT-INF/classpath.idx");
|
assertThat(jar(repackaged)).hasEntryWithName("BOOT-INF/classpath.idx");
|
||||||
|
try (JarFile jarFile = new JarFile(repackaged)) {
|
||||||
|
List<String> index = readClasspathIndex(jarFile, "BOOT-INF/classpath.idx");
|
||||||
|
assertThat(index).allMatch((entry) -> entry.startsWith("BOOT-INF/lib/"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -211,12 +211,17 @@ class WarIntegrationTests extends AbstractArchiveIntegrationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestTemplate
|
@TestTemplate
|
||||||
void repackagedWarDoesNotContainClasspathIndex(MavenBuild mavenBuild) {
|
void repackagedWarContainsClasspathIndex(MavenBuild mavenBuild) {
|
||||||
mavenBuild.project("war").execute((project) -> {
|
mavenBuild.project("war").execute((project) -> {
|
||||||
File repackaged = new File(project, "target/war-0.0.1.BUILD-SNAPSHOT.war");
|
File repackaged = new File(project, "target/war-0.0.1.BUILD-SNAPSHOT.war");
|
||||||
assertThat(jar(repackaged))
|
assertThat(jar(repackaged)).manifest(
|
||||||
.manifest((manifest) -> manifest.doesNotHaveAttribute("Spring-Boot-Classpath-Index"));
|
(manifest) -> manifest.hasAttribute("Spring-Boot-Classpath-Index", "WEB-INF/classpath.idx"));
|
||||||
assertThat(jar(repackaged)).doesNotHaveEntryWithName("BOOT-INF/classpath.idx");
|
assertThat(jar(repackaged)).hasEntryWithName("WEB-INF/classpath.idx");
|
||||||
|
try (JarFile jarFile = new JarFile(repackaged)) {
|
||||||
|
List<String> index = readClasspathIndex(jarFile, "WEB-INF/classpath.idx");
|
||||||
|
assertThat(index).allMatch(
|
||||||
|
(entry) -> entry.startsWith("WEB-INF/lib/") || entry.startsWith("WEB-INF/lib-provided/"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue