Skip to content

Commit

Permalink
Add classpath index support for exploded war archives
Browse files Browse the repository at this point in the history
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
  • Loading branch information
scottfrederick committed Dec 9, 2021
1 parent 8b5600f commit 8f57f0b
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
*
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @since 2.0.0
*/
public class BootWar extends War implements BootArchive {
Expand All @@ -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<String> mainClass;
Expand Down Expand Up @@ -91,8 +94,8 @@ private Object getProvidedLibFiles() {

@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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,7 @@ void whenJarIsLayeredThenLayersIndexIsPresentAndCorrect() throws IOException {
expected.add("- \"application\":");
Set<String> 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);
Expand Down Expand Up @@ -551,9 +549,7 @@ void whenJarIsLayeredWithCustomStrategiesThenLayersIndexIsPresentAndCorrect() th
Set<String> 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/\"");
Expand Down Expand Up @@ -634,12 +630,14 @@ File createLayeredJar(Action<LayeredSpec> action) throws IOException {
return getTask().getArchiveFile().get().getAsFile();
}

abstract void applyLayered(Action<LayeredSpec> action);

boolean archiveHasClasspathIndex() {
return true;
File createPopulatedJar() throws IOException {
addContent();
executeTask();
return getTask().getArchiveFile().get().getAsFile();
}

abstract void applyLayered(Action<LayeredSpec> action);

@SuppressWarnings("unchecked")
void addContent() throws IOException {
this.task.getMainClass().set("com.example.Main");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,6 @@ void classpathIndexPointsToBootInfLibs() throws IOException {
}
}

private File createPopulatedJar() throws IOException {
addContent();
executeTask();
return getTask().getArchiveFile().get().getAsFile();
}

@Override
void applyLayered(Action<LayeredSpec> action) {
getTask().layered(action);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* Integration tests for {@link BootWar}.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
@GradleCompatibility(configurationCache = true)
class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
Expand All @@ -37,7 +38,7 @@ class BootWarIntegrationTests extends AbstractBootArchiveIntegrationTests {
@Override
String[] getExpectedApplicationLayerContents(String... 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]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
* Tests for {@link BootWar}.
*
* @author Andy Wilkinson
* @author Scott Frederick
*/
class BootWarTests extends AbstractBootArchiveTests<BootWar> {

Expand Down Expand Up @@ -109,6 +110,28 @@ void libProvidedEntriesAreWrittenAfterLibEntries() throws IOException {
.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();
Expand All @@ -124,9 +147,4 @@ void applyLayered(Action<LayeredSpec> action) {
getTask().layered(action);
}

@Override
boolean archiveHasClasspathIndex() {
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ public String getClassesLocation() {
return "WEB-INF/classes/";
}

@Override
public String getClasspathIndexFileLocation() {
return "WEB-INF/classpath.idx";
}

@Override
public String getLayersIndexFileLocation() {
return "WEB-INF/layers.idx";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -21,16 +21,19 @@
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.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
* @since 1.0.0
*/
public abstract class ExecutableArchiveLauncher extends Launcher {
Expand All @@ -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;
Expand All @@ -64,9 +69,21 @@ protected ExecutableArchiveLauncher(Archive archive) {
}

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();
Expand Down Expand Up @@ -133,7 +150,10 @@ private Iterator<Archive> applyClassPathArchivePostProcessing(Iterator<Archive>
* @since 2.3.0
*/
protected boolean isSearchCandidate(Archive.Entry entry) {
return true;
if (getArchiveEntryPathPrefix() == null) {
return true;
}
return entry.getName().startsWith(getArchiveEntryPathPrefix());
}

/**
Expand Down Expand Up @@ -166,6 +186,14 @@ protected boolean isPostProcessingClassPathArchives() {
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
protected boolean isExploded() {
return this.archive.isExploded();
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -32,12 +27,11 @@
* @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/");
Expand All @@ -52,36 +46,19 @@ protected JarLauncher(Archive 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
protected boolean isPostProcessingClassPathArchives() {
return false;
}

@Override
protected boolean isSearchCandidate(Archive.Entry entry) {
return entry.getName().startsWith("BOOT-INF/");
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
}

@Override
protected boolean isNestedArchive(Archive.Entry entry) {
return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
protected String getArchiveEntryPathPrefix() {
return "BOOT-INF/";
}

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
Expand All @@ -26,6 +25,7 @@
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Scott Frederick
* @since 1.0.0
*/
public class WarLauncher extends ExecutableArchiveLauncher {
Expand All @@ -42,11 +42,6 @@ protected boolean isPostProcessingClassPathArchives() {
return false;
}

@Override
protected boolean isSearchCandidate(Entry entry) {
return entry.getName().startsWith("WEB-INF/");
}

@Override
public boolean isNestedArchive(Archive.Entry entry) {
if (entry.isDirectory()) {
Expand All @@ -55,6 +50,11 @@ public boolean isNestedArchive(Archive.Entry entry) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -47,6 +47,7 @@
*
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Scott Frederick
*/
public abstract class AbstractExecutableArchiveLauncherTests {

Expand Down Expand Up @@ -80,9 +81,9 @@ protected File createJarArchive(String name, Manifest manifest, String entryPref
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();
}
Expand Down
Loading

0 comments on commit 8f57f0b

Please sign in to comment.