diff --git a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/IndexedJarStructure.java b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/IndexedJarStructure.java index c4c7dc266ffe..e7d99136065b 100644 --- a/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/IndexedJarStructure.java +++ b/loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/IndexedJarStructure.java @@ -45,6 +45,7 @@ * * @author Stephane Nicoll * @author Moritz Halbritter + * @author Mohan Krishna Namburu */ class IndexedJarStructure implements JarStructure { @@ -54,9 +55,13 @@ class IndexedJarStructure implements JarStructure { private static final Set ENTRY_IGNORE_LIST = Set.of("META-INF/", "META-INF/MANIFEST.MF", "META-INF/services/java.nio.file.spi.FileSystemProvider"); + private static final String WAR_LIB_LOCATION = "WEB-INF/lib/"; + + private static final String WAR_LIB_PROVIDED_LOCATION = "WEB-INF/lib-provided/"; + private final Manifest originalManifest; - private final String libLocation; + private final List libLocations; private final String classesLocation; @@ -64,11 +69,20 @@ class IndexedJarStructure implements JarStructure { IndexedJarStructure(Manifest originalManifest, String indexFile) { this.originalManifest = originalManifest; - this.libLocation = getLocation(originalManifest, "Spring-Boot-Lib"); + this.libLocations = getLibLocations(originalManifest); this.classesLocation = getLocation(originalManifest, "Spring-Boot-Classes"); this.classpathEntries = readIndexFile(indexFile); } + private static List getLibLocations(Manifest manifest) { + String libLocation = getLocation(manifest, "Spring-Boot-Lib"); + if (WAR_LIB_LOCATION.equals(libLocation)) { + // An executable war also has its provided libraries on the classpath + return List.of(libLocation, WAR_LIB_PROVIDED_LOCATION); + } + return List.of(libLocation); + } + private static String getLocation(Manifest manifest, String attribute) { String location = getMandatoryAttribute(manifest, attribute); return (!location.endsWith("/")) ? location + "/" : location; @@ -130,8 +144,12 @@ public Manifest createLauncherManifest(UnaryOperator libraryTransformer) } private String toStructureDependency(String libEntryName) { - Assert.state(libEntryName.startsWith(this.libLocation), () -> "Invalid library location " + libEntryName); - return libEntryName.substring(this.libLocation.length()); + for (String libLocation : this.libLocations) { + if (libEntryName.startsWith(libLocation)) { + return libEntryName.substring(libLocation.length()); + } + } + throw new IllegalStateException("Invalid library location " + libEntryName); } private static String getMandatoryAttribute(Manifest manifest, String attribute) { diff --git a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/IndexedJarStructureTests.java b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/IndexedJarStructureTests.java index 0585b824254b..2209153f5a1d 100644 --- a/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/IndexedJarStructureTests.java +++ b/loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/IndexedJarStructureTests.java @@ -35,11 +35,13 @@ 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}. * * @author Moritz Halbritter + * @author Mohan Krishna Namburu */ class IndexedJarStructureTests { @@ -97,6 +99,64 @@ void shouldCreateLauncherManifest() throws IOException { "Spring-Boot-Layers-Index"); } + @Test + void shouldResolveLibraryEntryOfWar() throws IOException { + IndexedJarStructure structure = createWarStructure(); + Entry entry = structure.resolve("WEB-INF/lib/spring-web-6.1.4.jar"); + assertThat(entry).isNotNull(); + assertThat(entry.location()).isEqualTo("spring-web-6.1.4.jar"); + assertThat(entry.originalLocation()).isEqualTo("WEB-INF/lib/spring-web-6.1.4.jar"); + assertThat(entry.type()).isEqualTo(Type.LIBRARY); + } + + @Test + void shouldResolveProvidedLibraryEntryOfWar() 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 shouldResolveApplicationEntryOfWar() throws IOException { + IndexedJarStructure structure = createWarStructure(); + Entry entry = structure.resolve("WEB-INF/classes/application.properties"); + assertThat(entry).isNotNull(); + assertThat(entry.location()).isEqualTo("application.properties"); + assertThat(entry.originalLocation()).isEqualTo("WEB-INF/classes/application.properties"); + assertThat(entry.type()).isEqualTo(Type.APPLICATION_CLASS_OR_RESOURCE); + } + + @Test + void shouldNotResolveNonExistingProvidedLibsOfWar() throws IOException { + IndexedJarStructure structure = createWarStructure(); + Entry entry = structure.resolve("WEB-INF/lib-provided/doesnt-exists.jar"); + assertThat(entry).isNull(); + } + + @Test + void shouldCreateLauncherManifestForWar() throws IOException { + IndexedJarStructure structure = createWarStructure(); + Manifest manifest = structure.createLauncherManifest(UnaryOperator.identity()); + Map attributes = getAttributes(manifest); + assertThat(attributes).containsEntry("Class-Path", + "spring-web-6.1.4.jar spring-core-6.1.4.jar spring-boot-web-server-4.1.0.jar tomcat-embed-core-10.1.19.jar") + .containsEntry("Main-Class", "org.springframework.boot.jarmode.tools.IndexedJarStructureTests") + .doesNotContainKeys("Start-Class", "Spring-Boot-Classes", "Spring-Boot-Lib", "Spring-Boot-Classpath-Index", + "Spring-Boot-Layers-Index"); + } + + @Test + void shouldFailWhenLibraryIsOutsideOfKnownLocations() throws IOException { + IndexedJarStructure structure = new IndexedJarStructure(createManifest(), """ + - "BOOT-INF/other/spring-web-6.1.4.jar" + """); + assertThatIllegalStateException().isThrownBy(() -> structure.createLauncherManifest(UnaryOperator.identity())) + .withMessage("Invalid library location BOOT-INF/other/spring-web-6.1.4.jar"); + } + @Test void shouldLoadFromFile(@TempDir File tempDir) throws IOException { File jarFile = new File(tempDir, "test.jar"); @@ -157,6 +217,35 @@ private String createIndexFile() { """; } + private IndexedJarStructure createWarStructure() throws IOException { + return new IndexedJarStructure(createWarManifest(), createWarIndexFile()); + } + + private String createWarIndexFile() { + return """ + - "WEB-INF/lib/spring-web-6.1.4.jar" + - "WEB-INF/lib/spring-core-6.1.4.jar" + - "WEB-INF/lib-provided/spring-boot-web-server-4.1.0.jar" + - "WEB-INF/lib-provided/tomcat-embed-core-10.1.19.jar" + """; + } + + private Manifest createWarManifest() throws IOException { + return 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 + Spring-Boot-Layers-Index: WEB-INF/layers.idx + Build-Jdk-Spec: 17 + Implementation-Title: IndexedJarStructureTests + Implementation-Version: 0.0.1-SNAPSHOT + """.getBytes(StandardCharsets.UTF_8))); + } + private Manifest createManifest() throws IOException { return new Manifest(new ByteArrayInputStream(""" Manifest-Version: 1.0