Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,17 @@ class BootArchiveSupport {
}

void configureManifest(Manifest manifest, String mainClass, String classes, String lib,
@Nullable String classPathIndex, @Nullable String layersIndex, String jdkVersion,
String implementationTitle, @Nullable Object implementationVersion) {
@Nullable String libProvided, @Nullable String classPathIndex, @Nullable String layersIndex,
String jdkVersion, String implementationTitle, @Nullable Object implementationVersion) {
Attributes attributes = manifest.getAttributes();
attributes.putIfAbsent("Main-Class", this.loaderMainClass);
attributes.putIfAbsent("Start-Class", mainClass);
attributes.computeIfAbsent("Spring-Boot-Version", (name) -> determineSpringBootVersion());
attributes.putIfAbsent("Spring-Boot-Classes", classes);
attributes.putIfAbsent("Spring-Boot-Lib", lib);
if (libProvided != null) {
attributes.putIfAbsent("Spring-Boot-Lib-Provided", libProvided);
}
if (classPathIndex != null) {
attributes.putIfAbsent("Spring-Boot-Classpath-Index", classPathIndex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ ResolvedDependencies getResolvedDependencies() {

@Override
public void copy() {
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY, null,
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get());
super.copy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ ResolvedDependencies getResolvedDependencies() {
@Override
public void copy() {
this.support.configureManifest(getManifest(), getMainClass().get(), CLASSES_DIRECTORY, LIB_DIRECTORY,
CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
LIB_PROVIDED_DIRECTORY, CLASSPATH_INDEX, (isLayeredDisabled()) ? null : LAYERS_INDEX,
this.getTargetJavaVersion().get().getMajorVersion(), this.projectName.get(), this.projectVersion.get());
super.copy();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
class IndexedJarStructure implements JarStructure {

private static final List<String> MANIFEST_DENY_LIST = List.of("Start-Class", "Spring-Boot-Classes",
"Spring-Boot-Lib", "Spring-Boot-Classpath-Index", "Spring-Boot-Layers-Index");
"Spring-Boot-Lib", "Spring-Boot-Lib-Provided", "Spring-Boot-Classpath-Index", "Spring-Boot-Layers-Index");

private static final Set<String> ENTRY_IGNORE_LIST = Set.of("META-INF/", "META-INF/MANIFEST.MF",
"META-INF/services/java.nio.file.spi.FileSystemProvider");
Expand All @@ -58,13 +58,16 @@ class IndexedJarStructure implements JarStructure {

private final String libLocation;

private final @Nullable String providedLibLocation;

private final String classesLocation;

private final List<String> classpathEntries;

IndexedJarStructure(Manifest originalManifest, String indexFile) {
this.originalManifest = originalManifest;
this.libLocation = getLocation(originalManifest, "Spring-Boot-Lib");
this.providedLibLocation = getOptionalLocation(originalManifest, "Spring-Boot-Lib-Provided");
this.classesLocation = getLocation(originalManifest, "Spring-Boot-Classes");
this.classpathEntries = readIndexFile(indexFile);
}
Expand All @@ -74,6 +77,14 @@ private static String getLocation(Manifest manifest, String attribute) {
return (!location.endsWith("/")) ? location + "/" : location;
}

private static @Nullable String getOptionalLocation(Manifest manifest, String attribute) {
String location = manifest.getMainAttributes().getValue(attribute);
if (!StringUtils.hasLength(location)) {
return null;
}
return (!location.endsWith("/")) ? location + "/" : location;
}

private static List<String> readIndexFile(String indexFile) {
String[] lines = Arrays.stream(indexFile.split("\n"))
.map((line) -> line.replace("\r", ""))
Expand Down Expand Up @@ -130,8 +141,13 @@ public Manifest createLauncherManifest(UnaryOperator<String> libraryTransformer)
}

private String toStructureDependency(String libEntryName) {
Assert.state(libEntryName.startsWith(this.libLocation), () -> "Invalid library location " + libEntryName);
return libEntryName.substring(this.libLocation.length());
if (libEntryName.startsWith(this.libLocation)) {
return libEntryName.substring(this.libLocation.length());
}
if (this.providedLibLocation != null && libEntryName.startsWith(this.providedLibLocation)) {
return libEntryName.substring(this.providedLibLocation.length());
}
throw new IllegalStateException("Invalid library location " + libEntryName);
}

private static String getMandatoryAttribute(Manifest manifest, String attribute) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.springframework.boot.jarmode.tools.JarStructure.Entry.Type;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;

/**
* Tests for {@link IndexedJarStructure}.
Expand All @@ -53,6 +54,24 @@ void shouldResolveLibraryEntry() throws IOException {
assertThat(entry.type()).isEqualTo(Type.LIBRARY);
}

@Test
void shouldResolveLibraryEntryFromWarProvidedLocation() throws IOException {
IndexedJarStructure structure = createWarStructure();
Entry entry = structure.resolve("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar");
assertThat(entry).isNotNull();
assertThat(entry.location()).isEqualTo("tomcat-embed-core-10.1.19.jar");
assertThat(entry.originalLocation()).isEqualTo("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar");
assertThat(entry.type()).isEqualTo(Type.LIBRARY);
}

@Test
void shouldCreateLauncherManifestForWarWithProvidedLibraries() throws IOException {
IndexedJarStructure structure = createWarStructure();
Manifest manifest = structure.createLauncherManifest(UnaryOperator.identity());
assertThat(getAttributes(manifest)).containsEntry("Class-Path",
"spring-webmvc-6.1.4.jar tomcat-embed-core-10.1.19.jar");
}

@Test
void shouldResolveApplicationEntry() throws IOException {
IndexedJarStructure structure = createStructure();
Expand Down Expand Up @@ -119,10 +138,62 @@ private Map<String, String> getAttributes(Manifest manifest) {
return result;
}

@Test
void shouldFailToResolveProvidedLibraryWhenAttributeIsMissing() throws IOException {
// an archive whose index references lib-provided but whose manifest does not
// record the location has nothing to flatten the entry against
IndexedJarStructure structure = createWarStructureWithoutProvidedLibAttribute();
assertThatIllegalStateException()
.isThrownBy(() -> structure.resolve("WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"))
.withMessageContaining("Invalid library location");
}

@Test
void launcherManifestShouldNotContainProvidedLibAttribute() throws IOException {
IndexedJarStructure structure = createWarStructure();
Manifest manifest = structure.createLauncherManifest(UnaryOperator.identity());
assertThat(getAttributes(manifest)).doesNotContainKey("Spring-Boot-Lib-Provided");
}

private IndexedJarStructure createStructure() throws IOException {
return new IndexedJarStructure(createManifest(), createIndexFile());
}

private IndexedJarStructure createWarStructure() throws IOException {
Manifest manifest = new Manifest(new ByteArrayInputStream("""
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.WarLauncher
Start-Class: org.springframework.boot.jarmode.tools.IndexedJarStructureTests
Spring-Boot-Version: 3.3.0-SNAPSHOT
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Spring-Boot-Lib-Provided: WEB-INF/lib-provided/
Spring-Boot-Classpath-Index: WEB-INF/classpath.idx
""".getBytes(StandardCharsets.UTF_8)));
String indexFile = """
- "WEB-INF/lib/spring-webmvc-6.1.4.jar"
- "WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"
""";
return new IndexedJarStructure(manifest, indexFile);
}

private IndexedJarStructure createWarStructureWithoutProvidedLibAttribute() throws IOException {
Manifest manifest = new Manifest(new ByteArrayInputStream("""
Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.launch.WarLauncher
Start-Class: org.springframework.boot.jarmode.tools.IndexedJarStructureTests
Spring-Boot-Version: 3.3.0-SNAPSHOT
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Spring-Boot-Classpath-Index: WEB-INF/classpath.idx
""".getBytes(StandardCharsets.UTF_8)));
String indexFile = """
- "WEB-INF/lib/spring-webmvc-6.1.4.jar"
- "WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar"
""";
return new IndexedJarStructure(manifest, indexFile);
}

private String createIndexFile() {
return """
- "BOOT-INF/lib/spring-webmvc-6.1.4.jar"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.springframework.boot.loader.tools.AbstractJarWriter.UnpackHandler;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
Expand All @@ -69,6 +70,8 @@ public abstract class Packager {

private static final String BOOT_LIB_ATTRIBUTE = "Spring-Boot-Lib";

private static final String BOOT_LIB_PROVIDED_ATTRIBUTE = "Spring-Boot-Lib-Provided";

private static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index";

private static final String BOOT_LAYERS_INDEX_ATTRIBUTE = "Spring-Boot-Layers-Index";
Expand Down Expand Up @@ -406,7 +409,12 @@ private void addBootAttributesForLayout(Attributes attributes) {
else {
attributes.putValue(BOOT_CLASSES_ATTRIBUTE, layout.getClassesLocation());
}
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, getLayout().getLibraryLocation("", LibraryScope.COMPILE));
String libraryLocation = getLayout().getLibraryLocation("", LibraryScope.COMPILE);
putIfHasLength(attributes, BOOT_LIB_ATTRIBUTE, libraryLocation);
String providedLibraryLocation = getLayout().getLibraryLocation("", LibraryScope.PROVIDED);
if (!ObjectUtils.nullSafeEquals(providedLibraryLocation, libraryLocation)) {
putIfHasLength(attributes, BOOT_LIB_PROVIDED_ATTRIBUTE, providedLibraryLocation);
}
putIfHasLength(attributes, BOOT_CLASSPATH_INDEX_ATTRIBUTE, layout.getClasspathIndexFileLocation());
if (isLayered()) {
putIfHasLength(attributes, BOOT_LAYERS_INDEX_ATTRIBUTE, layout.getLayersIndexFileLocation());
Expand Down
Loading