Skip to content

Commit e5f489f

Browse files
committed
Restore manifest support for nested directory jars
Update `NestedJarFile` so that the `getManifest()` method returns the manifest from the parent jar file for nested jars based on directory entries. This restores the previous behavior supported by Spring Boot 3.1 and allows class methods such as `getPackage().getImplementationVersion()` to return non `null` results. Fixes gh-38996
1 parent 36f00fc commit e5f489f

File tree

5 files changed

+120
-11
lines changed

5 files changed

+120
-11
lines changed

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFile.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ public InputStream getRawZipDataInputStream() throws IOException {
154154
@Override
155155
public Manifest getManifest() throws IOException {
156156
try {
157-
return this.resources.zipContent().getInfo(ManifestInfo.class, this::getManifestInfo).getManifest();
157+
return this.resources.zipContentForManifest()
158+
.getInfo(ManifestInfo.class, this::getManifestInfo)
159+
.getManifest();
158160
}
159161
catch (UncheckedIOException ex) {
160162
throw ex.getCause();

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/NestedJarFileResources.java

+31
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import org.springframework.boot.loader.ref.Cleaner;
3232
import org.springframework.boot.loader.zip.ZipContent;
33+
import org.springframework.boot.loader.zip.ZipContent.Kind;
3334

3435
/**
3536
* Resources created managed and cleaned by a {@link NestedJarFile} instance and suitable
@@ -43,6 +44,8 @@ class NestedJarFileResources implements Runnable {
4344

4445
private ZipContent zipContent;
4546

47+
private ZipContent zipContentForManifest;
48+
4649
private final Set<InputStream> inputStreams = Collections.newSetFromMap(new WeakHashMap<>());
4750

4851
private Deque<Inflater> inflaterCache = new ArrayDeque<>();
@@ -55,6 +58,8 @@ class NestedJarFileResources implements Runnable {
5558
*/
5659
NestedJarFileResources(File file, String nestedEntryName) throws IOException {
5760
this.zipContent = ZipContent.open(file.toPath(), nestedEntryName);
61+
this.zipContentForManifest = (this.zipContent.getKind() != Kind.NESTED_DIRECTORY) ? null
62+
: ZipContent.open(file.toPath());
5863
}
5964

6065
/**
@@ -65,6 +70,15 @@ ZipContent zipContent() {
6570
return this.zipContent;
6671
}
6772

73+
/**
74+
* Return the underlying {@link ZipContent} that should be used to load manifest
75+
* content.
76+
* @return the zip content to use when loading the manifest
77+
*/
78+
ZipContent zipContentForManifest() {
79+
return (this.zipContentForManifest != null) ? this.zipContentForManifest : this.zipContent;
80+
}
81+
6882
/**
6983
* Add a managed input stream resource.
7084
* @param inputStream the input stream
@@ -144,6 +158,7 @@ private void releaseAll() {
144158
exceptionChain = releaseInflators(exceptionChain);
145159
exceptionChain = releaseInputStreams(exceptionChain);
146160
exceptionChain = releaseZipContent(exceptionChain);
161+
exceptionChain = releaseZipContentForManifest(exceptionChain);
147162
if (exceptionChain != null) {
148163
throw new UncheckedIOException(exceptionChain);
149164
}
@@ -195,6 +210,22 @@ private IOException releaseZipContent(IOException exceptionChain) {
195210
return exceptionChain;
196211
}
197212

213+
private IOException releaseZipContentForManifest(IOException exceptionChain) {
214+
ZipContent zipContentForManifest = this.zipContentForManifest;
215+
if (zipContentForManifest != null) {
216+
try {
217+
zipContentForManifest.close();
218+
}
219+
catch (IOException ex) {
220+
exceptionChain = addToExceptionChain(exceptionChain, ex);
221+
}
222+
finally {
223+
this.zipContentForManifest = null;
224+
}
225+
}
226+
return exceptionChain;
227+
}
228+
198229
private IOException addToExceptionChain(IOException exceptionChain, IOException ex) {
199230
if (exceptionChain != null) {
200231
exceptionChain.addSuppressed(ex);

spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/zip/ZipContent.java

+46-10
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public final class ZipContent implements Closeable {
7272

7373
private final Source source;
7474

75+
private final Kind kind;
76+
7577
private final FileChannelDataBlock data;
7678

7779
private final long centralDirectoryPos;
@@ -94,10 +96,11 @@ public final class ZipContent implements Closeable {
9496

9597
private SoftReference<Map<Class<?>, Object>> info;
9698

97-
private ZipContent(Source source, FileChannelDataBlock data, long centralDirectoryPos, long commentPos,
99+
private ZipContent(Source source, Kind kind, FileChannelDataBlock data, long centralDirectoryPos, long commentPos,
98100
long commentLength, int[] lookupIndexes, int[] nameHashLookups, int[] relativeCentralDirectoryOffsetLookups,
99101
NameOffsetLookups nameOffsetLookups, boolean hasJarSignatureFile) {
100102
this.source = source;
103+
this.kind = kind;
101104
this.data = data;
102105
this.centralDirectoryPos = centralDirectoryPos;
103106
this.commentPos = commentPos;
@@ -109,6 +112,15 @@ private ZipContent(Source source, FileChannelDataBlock data, long centralDirecto
109112
this.hasJarSignatureFile = hasJarSignatureFile;
110113
}
111114

115+
/**
116+
* Return the kind of content that was loaded.
117+
* @return the content kind
118+
* @since 3.2.2
119+
*/
120+
public Kind getKind() {
121+
return this.kind;
122+
}
123+
112124
/**
113125
* Open a {@link DataBlock} containing the raw zip data. For container zip files, this
114126
* may be smaller than the original file since additional bytes are permitted at the
@@ -380,6 +392,30 @@ private static ZipContent open(Source source) throws IOException {
380392
return zipContent;
381393
}
382394

395+
/**
396+
* Zip content kinds.
397+
*
398+
* @since 3.2.2
399+
*/
400+
public enum Kind {
401+
402+
/**
403+
* Content from a standard zip file.
404+
*/
405+
ZIP,
406+
407+
/**
408+
* Content from nested zip content.
409+
*/
410+
NESTED_ZIP,
411+
412+
/**
413+
* Content from a nested zip directory.
414+
*/
415+
NESTED_DIRECTORY
416+
417+
}
418+
383419
/**
384420
* The source of {@link ZipContent}. Used as a cache key.
385421
*
@@ -451,7 +487,7 @@ private void add(ZipCentralDirectoryFileHeaderRecord centralRecord, long pos, bo
451487
this.cursor++;
452488
}
453489

454-
private ZipContent finish(long commentPos, long commentLength, boolean hasJarSignatureFile) {
490+
private ZipContent finish(Kind kind, long commentPos, long commentLength, boolean hasJarSignatureFile) {
455491
if (this.cursor != this.nameHashLookups.length) {
456492
this.nameHashLookups = Arrays.copyOf(this.nameHashLookups, this.cursor);
457493
this.relativeCentralDirectoryOffsetLookups = Arrays.copyOf(this.relativeCentralDirectoryOffsetLookups,
@@ -463,7 +499,7 @@ private ZipContent finish(long commentPos, long commentLength, boolean hasJarSig
463499
for (int i = 0; i < size; i++) {
464500
lookupIndexes[this.index[i]] = i;
465501
}
466-
return new ZipContent(this.source, this.data, this.centralDirectoryPos, commentPos, commentLength,
502+
return new ZipContent(this.source, kind, this.data, this.centralDirectoryPos, commentPos, commentLength,
467503
lookupIndexes, this.nameHashLookups, this.relativeCentralDirectoryOffsetLookups,
468504
this.nameOffsetLookups, hasJarSignatureFile);
469505
}
@@ -525,7 +561,7 @@ static ZipContent load(Source source) throws IOException {
525561

526562
private static ZipContent loadNonNested(Source source) throws IOException {
527563
debug.log("Loading non-nested zip '%s'", source.path());
528-
return openAndLoad(source, new FileChannelDataBlock(source.path()));
564+
return openAndLoad(source, Kind.ZIP, new FileChannelDataBlock(source.path()));
529565
}
530566

531567
private static ZipContent loadNestedZip(Source source, Entry entry) throws IOException {
@@ -534,21 +570,21 @@ private static ZipContent loadNestedZip(Source source, Entry entry) throws IOExc
534570
.formatted(source.nestedEntryName(), source.path()));
535571
}
536572
debug.log("Loading nested zip entry '%s' from '%s'", source.nestedEntryName(), source.path());
537-
return openAndLoad(source, entry.getContent());
573+
return openAndLoad(source, Kind.NESTED_ZIP, entry.getContent());
538574
}
539575

540-
private static ZipContent openAndLoad(Source source, FileChannelDataBlock data) throws IOException {
576+
private static ZipContent openAndLoad(Source source, Kind kind, FileChannelDataBlock data) throws IOException {
541577
try {
542578
data.open();
543-
return loadContent(source, data);
579+
return loadContent(source, kind, data);
544580
}
545581
catch (IOException | RuntimeException ex) {
546582
data.close();
547583
throw ex;
548584
}
549585
}
550586

551-
private static ZipContent loadContent(Source source, FileChannelDataBlock data) throws IOException {
587+
private static ZipContent loadContent(Source source, Kind kind, FileChannelDataBlock data) throws IOException {
552588
ZipEndOfCentralDirectoryRecord.Located locatedEocd = ZipEndOfCentralDirectoryRecord.load(data);
553589
ZipEndOfCentralDirectoryRecord eocd = locatedEocd.endOfCentralDirectoryRecord();
554590
long eocdPos = locatedEocd.pos();
@@ -585,7 +621,7 @@ private static ZipContent loadContent(Source source, FileChannelDataBlock data)
585621
pos += centralRecord.size();
586622
}
587623
long commentPos = locatedEocd.pos() + ZipEndOfCentralDirectoryRecord.COMMENT_OFFSET;
588-
return loader.finish(commentPos, eocd.commentLength(), hasJarSignatureFile);
624+
return loader.finish(kind, commentPos, eocd.commentLength(), hasJarSignatureFile);
589625
}
590626

591627
/**
@@ -642,7 +678,7 @@ private static ZipContent loadNestedDirectory(Source source, ZipContent zip, Ent
642678
}
643679
}
644680
}
645-
return loader.finish(zip.commentPos, zip.commentLength, zip.hasJarSignatureFile);
681+
return loader.finish(Kind.NESTED_DIRECTORY, zip.commentPos, zip.commentLength, zip.hasJarSignatureFile);
646682
}
647683
catch (IOException | RuntimeException ex) {
648684
zip.data.close();

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/jar/NestedJarFileTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,26 @@ void createWhenNestedJarDirectoryOpensJar() throws IOException {
110110
}
111111
}
112112

113+
@Test
114+
void getManifestWhenNestedJarReturnsManifestOfNestedJar() throws Exception {
115+
try (JarFile jar = new JarFile(this.file)) {
116+
try (NestedJarFile nestedJar = new NestedJarFile(this.file, "nested.jar")) {
117+
Manifest manifest = nestedJar.getManifest();
118+
assertThat(manifest).isNotEqualTo(jar.getManifest());
119+
assertThat(manifest.getMainAttributes().getValue("Built-By")).isEqualTo("j2");
120+
}
121+
}
122+
}
123+
124+
@Test
125+
void getManifestWhenNestedJarDirectoryReturnsManifestOfParent() throws Exception {
126+
try (JarFile jar = new JarFile(this.file)) {
127+
try (NestedJarFile nestedJar = new NestedJarFile(this.file, "d/")) {
128+
assertThat(nestedJar.getManifest()).isEqualTo(jar.getManifest());
129+
}
130+
}
131+
}
132+
113133
@Test
114134
void createWhenJarHasFrontMatterOpensJar() throws IOException {
115135
File file = new File(this.tempDir, "frontmatter.jar");

spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/zip/ZipContentTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
import org.springframework.boot.loader.testsupport.TestJar;
5050
import org.springframework.boot.loader.zip.ZipContent.Entry;
51+
import org.springframework.boot.loader.zip.ZipContent.Kind;
5152
import org.springframework.util.FileCopyUtils;
5253
import org.springframework.util.StreamUtils;
5354

@@ -168,6 +169,25 @@ void getEntryAsCreatesCompatibleEntries() throws IOException {
168169
}
169170
}
170171

172+
@Test
173+
void getKindWhenZipReturnsZip() {
174+
assertThat(this.zipContent.getKind()).isEqualTo(Kind.ZIP);
175+
}
176+
177+
@Test
178+
void getKindWhenNestedZipReturnsNestedZip() throws IOException {
179+
try (ZipContent nested = ZipContent.open(this.file.toPath(), "nested.jar")) {
180+
assertThat(nested.getKind()).isEqualTo(Kind.NESTED_ZIP);
181+
}
182+
}
183+
184+
@Test
185+
void getKindWhenNestedDirectoryReturnsNestedDirectory() throws IOException {
186+
try (ZipContent nested = ZipContent.open(this.file.toPath(), "d/")) {
187+
assertThat(nested.getKind()).isEqualTo(Kind.NESTED_DIRECTORY);
188+
}
189+
}
190+
171191
private void assertThatFieldsAreEqual(ZipEntry actual, ZipEntry expected) {
172192
assertThat(actual.getName()).isEqualTo(expected.getName());
173193
assertThat(actual.getTime()).isEqualTo(expected.getTime());

0 commit comments

Comments
 (0)