mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Merge branch '3.2.x'
Closes gh-39071
This commit is contained in:
commit
2d12fa073d
@ -154,7 +154,9 @@ public class NestedJarFile extends JarFile {
|
|||||||
@Override
|
@Override
|
||||||
public Manifest getManifest() throws IOException {
|
public Manifest getManifest() throws IOException {
|
||||||
try {
|
try {
|
||||||
return this.resources.zipContent().getInfo(ManifestInfo.class, this::getManifestInfo).getManifest();
|
return this.resources.zipContentForManifest()
|
||||||
|
.getInfo(ManifestInfo.class, this::getManifestInfo)
|
||||||
|
.getManifest();
|
||||||
}
|
}
|
||||||
catch (UncheckedIOException ex) {
|
catch (UncheckedIOException ex) {
|
||||||
throw ex.getCause();
|
throw ex.getCause();
|
||||||
|
@ -30,6 +30,7 @@ import java.util.zip.Inflater;
|
|||||||
|
|
||||||
import org.springframework.boot.loader.ref.Cleaner;
|
import org.springframework.boot.loader.ref.Cleaner;
|
||||||
import org.springframework.boot.loader.zip.ZipContent;
|
import org.springframework.boot.loader.zip.ZipContent;
|
||||||
|
import org.springframework.boot.loader.zip.ZipContent.Kind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resources created managed and cleaned by a {@link NestedJarFile} instance and suitable
|
* Resources created managed and cleaned by a {@link NestedJarFile} instance and suitable
|
||||||
@ -43,6 +44,8 @@ class NestedJarFileResources implements Runnable {
|
|||||||
|
|
||||||
private ZipContent zipContent;
|
private ZipContent zipContent;
|
||||||
|
|
||||||
|
private ZipContent zipContentForManifest;
|
||||||
|
|
||||||
private final Set<InputStream> inputStreams = Collections.newSetFromMap(new WeakHashMap<>());
|
private final Set<InputStream> inputStreams = Collections.newSetFromMap(new WeakHashMap<>());
|
||||||
|
|
||||||
private Deque<Inflater> inflaterCache = new ArrayDeque<>();
|
private Deque<Inflater> inflaterCache = new ArrayDeque<>();
|
||||||
@ -55,6 +58,8 @@ class NestedJarFileResources implements Runnable {
|
|||||||
*/
|
*/
|
||||||
NestedJarFileResources(File file, String nestedEntryName) throws IOException {
|
NestedJarFileResources(File file, String nestedEntryName) throws IOException {
|
||||||
this.zipContent = ZipContent.open(file.toPath(), nestedEntryName);
|
this.zipContent = ZipContent.open(file.toPath(), nestedEntryName);
|
||||||
|
this.zipContentForManifest = (this.zipContent.getKind() != Kind.NESTED_DIRECTORY) ? null
|
||||||
|
: ZipContent.open(file.toPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,6 +70,15 @@ class NestedJarFileResources implements Runnable {
|
|||||||
return this.zipContent;
|
return this.zipContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the underlying {@link ZipContent} that should be used to load manifest
|
||||||
|
* content.
|
||||||
|
* @return the zip content to use when loading the manifest
|
||||||
|
*/
|
||||||
|
ZipContent zipContentForManifest() {
|
||||||
|
return (this.zipContentForManifest != null) ? this.zipContentForManifest : this.zipContent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a managed input stream resource.
|
* Add a managed input stream resource.
|
||||||
* @param inputStream the input stream
|
* @param inputStream the input stream
|
||||||
@ -144,6 +158,7 @@ class NestedJarFileResources implements Runnable {
|
|||||||
exceptionChain = releaseInflators(exceptionChain);
|
exceptionChain = releaseInflators(exceptionChain);
|
||||||
exceptionChain = releaseInputStreams(exceptionChain);
|
exceptionChain = releaseInputStreams(exceptionChain);
|
||||||
exceptionChain = releaseZipContent(exceptionChain);
|
exceptionChain = releaseZipContent(exceptionChain);
|
||||||
|
exceptionChain = releaseZipContentForManifest(exceptionChain);
|
||||||
if (exceptionChain != null) {
|
if (exceptionChain != null) {
|
||||||
throw new UncheckedIOException(exceptionChain);
|
throw new UncheckedIOException(exceptionChain);
|
||||||
}
|
}
|
||||||
@ -195,6 +210,22 @@ class NestedJarFileResources implements Runnable {
|
|||||||
return exceptionChain;
|
return exceptionChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IOException releaseZipContentForManifest(IOException exceptionChain) {
|
||||||
|
ZipContent zipContentForManifest = this.zipContentForManifest;
|
||||||
|
if (zipContentForManifest != null) {
|
||||||
|
try {
|
||||||
|
zipContentForManifest.close();
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
exceptionChain = addToExceptionChain(exceptionChain, ex);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.zipContentForManifest = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exceptionChain;
|
||||||
|
}
|
||||||
|
|
||||||
private IOException addToExceptionChain(IOException exceptionChain, IOException ex) {
|
private IOException addToExceptionChain(IOException exceptionChain, IOException ex) {
|
||||||
if (exceptionChain != null) {
|
if (exceptionChain != null) {
|
||||||
exceptionChain.addSuppressed(ex);
|
exceptionChain.addSuppressed(ex);
|
||||||
|
@ -72,6 +72,8 @@ public final class ZipContent implements Closeable {
|
|||||||
|
|
||||||
private final Source source;
|
private final Source source;
|
||||||
|
|
||||||
|
private final Kind kind;
|
||||||
|
|
||||||
private final FileChannelDataBlock data;
|
private final FileChannelDataBlock data;
|
||||||
|
|
||||||
private final long centralDirectoryPos;
|
private final long centralDirectoryPos;
|
||||||
@ -94,10 +96,11 @@ public final class ZipContent implements Closeable {
|
|||||||
|
|
||||||
private SoftReference<Map<Class<?>, Object>> info;
|
private SoftReference<Map<Class<?>, Object>> info;
|
||||||
|
|
||||||
private ZipContent(Source source, FileChannelDataBlock data, long centralDirectoryPos, long commentPos,
|
private ZipContent(Source source, Kind kind, FileChannelDataBlock data, long centralDirectoryPos, long commentPos,
|
||||||
long commentLength, int[] lookupIndexes, int[] nameHashLookups, int[] relativeCentralDirectoryOffsetLookups,
|
long commentLength, int[] lookupIndexes, int[] nameHashLookups, int[] relativeCentralDirectoryOffsetLookups,
|
||||||
NameOffsetLookups nameOffsetLookups, boolean hasJarSignatureFile) {
|
NameOffsetLookups nameOffsetLookups, boolean hasJarSignatureFile) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
|
this.kind = kind;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.centralDirectoryPos = centralDirectoryPos;
|
this.centralDirectoryPos = centralDirectoryPos;
|
||||||
this.commentPos = commentPos;
|
this.commentPos = commentPos;
|
||||||
@ -109,6 +112,15 @@ public final class ZipContent implements Closeable {
|
|||||||
this.hasJarSignatureFile = hasJarSignatureFile;
|
this.hasJarSignatureFile = hasJarSignatureFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the kind of content that was loaded.
|
||||||
|
* @return the content kind
|
||||||
|
* @since 3.2.2
|
||||||
|
*/
|
||||||
|
public Kind getKind() {
|
||||||
|
return this.kind;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a {@link DataBlock} containing the raw zip data. For container zip files, this
|
* Open a {@link DataBlock} containing the raw zip data. For container zip files, this
|
||||||
* may be smaller than the original file since additional bytes are permitted at the
|
* may be smaller than the original file since additional bytes are permitted at the
|
||||||
@ -380,6 +392,30 @@ public final class ZipContent implements Closeable {
|
|||||||
return zipContent;
|
return zipContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zip content kinds.
|
||||||
|
*
|
||||||
|
* @since 3.2.2
|
||||||
|
*/
|
||||||
|
public enum Kind {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content from a standard zip file.
|
||||||
|
*/
|
||||||
|
ZIP,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content from nested zip content.
|
||||||
|
*/
|
||||||
|
NESTED_ZIP,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content from a nested zip directory.
|
||||||
|
*/
|
||||||
|
NESTED_DIRECTORY
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The source of {@link ZipContent}. Used as a cache key.
|
* The source of {@link ZipContent}. Used as a cache key.
|
||||||
*
|
*
|
||||||
@ -451,7 +487,7 @@ public final class ZipContent implements Closeable {
|
|||||||
this.cursor++;
|
this.cursor++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ZipContent finish(long commentPos, long commentLength, boolean hasJarSignatureFile) {
|
private ZipContent finish(Kind kind, long commentPos, long commentLength, boolean hasJarSignatureFile) {
|
||||||
if (this.cursor != this.nameHashLookups.length) {
|
if (this.cursor != this.nameHashLookups.length) {
|
||||||
this.nameHashLookups = Arrays.copyOf(this.nameHashLookups, this.cursor);
|
this.nameHashLookups = Arrays.copyOf(this.nameHashLookups, this.cursor);
|
||||||
this.relativeCentralDirectoryOffsetLookups = Arrays.copyOf(this.relativeCentralDirectoryOffsetLookups,
|
this.relativeCentralDirectoryOffsetLookups = Arrays.copyOf(this.relativeCentralDirectoryOffsetLookups,
|
||||||
@ -463,7 +499,7 @@ public final class ZipContent implements Closeable {
|
|||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
lookupIndexes[this.index[i]] = i;
|
lookupIndexes[this.index[i]] = i;
|
||||||
}
|
}
|
||||||
return new ZipContent(this.source, this.data, this.centralDirectoryPos, commentPos, commentLength,
|
return new ZipContent(this.source, kind, this.data, this.centralDirectoryPos, commentPos, commentLength,
|
||||||
lookupIndexes, this.nameHashLookups, this.relativeCentralDirectoryOffsetLookups,
|
lookupIndexes, this.nameHashLookups, this.relativeCentralDirectoryOffsetLookups,
|
||||||
this.nameOffsetLookups, hasJarSignatureFile);
|
this.nameOffsetLookups, hasJarSignatureFile);
|
||||||
}
|
}
|
||||||
@ -525,7 +561,7 @@ public final class ZipContent implements Closeable {
|
|||||||
|
|
||||||
private static ZipContent loadNonNested(Source source) throws IOException {
|
private static ZipContent loadNonNested(Source source) throws IOException {
|
||||||
debug.log("Loading non-nested zip '%s'", source.path());
|
debug.log("Loading non-nested zip '%s'", source.path());
|
||||||
return openAndLoad(source, new FileChannelDataBlock(source.path()));
|
return openAndLoad(source, Kind.ZIP, new FileChannelDataBlock(source.path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ZipContent loadNestedZip(Source source, Entry entry) throws IOException {
|
private static ZipContent loadNestedZip(Source source, Entry entry) throws IOException {
|
||||||
@ -534,13 +570,13 @@ public final class ZipContent implements Closeable {
|
|||||||
.formatted(source.nestedEntryName(), source.path()));
|
.formatted(source.nestedEntryName(), source.path()));
|
||||||
}
|
}
|
||||||
debug.log("Loading nested zip entry '%s' from '%s'", source.nestedEntryName(), source.path());
|
debug.log("Loading nested zip entry '%s' from '%s'", source.nestedEntryName(), source.path());
|
||||||
return openAndLoad(source, entry.getContent());
|
return openAndLoad(source, Kind.NESTED_ZIP, entry.getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ZipContent openAndLoad(Source source, FileChannelDataBlock data) throws IOException {
|
private static ZipContent openAndLoad(Source source, Kind kind, FileChannelDataBlock data) throws IOException {
|
||||||
try {
|
try {
|
||||||
data.open();
|
data.open();
|
||||||
return loadContent(source, data);
|
return loadContent(source, kind, data);
|
||||||
}
|
}
|
||||||
catch (IOException | RuntimeException ex) {
|
catch (IOException | RuntimeException ex) {
|
||||||
data.close();
|
data.close();
|
||||||
@ -548,7 +584,7 @@ public final class ZipContent implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ZipContent loadContent(Source source, FileChannelDataBlock data) throws IOException {
|
private static ZipContent loadContent(Source source, Kind kind, FileChannelDataBlock data) throws IOException {
|
||||||
ZipEndOfCentralDirectoryRecord.Located locatedEocd = ZipEndOfCentralDirectoryRecord.load(data);
|
ZipEndOfCentralDirectoryRecord.Located locatedEocd = ZipEndOfCentralDirectoryRecord.load(data);
|
||||||
ZipEndOfCentralDirectoryRecord eocd = locatedEocd.endOfCentralDirectoryRecord();
|
ZipEndOfCentralDirectoryRecord eocd = locatedEocd.endOfCentralDirectoryRecord();
|
||||||
long eocdPos = locatedEocd.pos();
|
long eocdPos = locatedEocd.pos();
|
||||||
@ -585,7 +621,7 @@ public final class ZipContent implements Closeable {
|
|||||||
pos += centralRecord.size();
|
pos += centralRecord.size();
|
||||||
}
|
}
|
||||||
long commentPos = locatedEocd.pos() + ZipEndOfCentralDirectoryRecord.COMMENT_OFFSET;
|
long commentPos = locatedEocd.pos() + ZipEndOfCentralDirectoryRecord.COMMENT_OFFSET;
|
||||||
return loader.finish(commentPos, eocd.commentLength(), hasJarSignatureFile);
|
return loader.finish(kind, commentPos, eocd.commentLength(), hasJarSignatureFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -642,7 +678,7 @@ public final class ZipContent implements Closeable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return loader.finish(zip.commentPos, zip.commentLength, zip.hasJarSignatureFile);
|
return loader.finish(Kind.NESTED_DIRECTORY, zip.commentPos, zip.commentLength, zip.hasJarSignatureFile);
|
||||||
}
|
}
|
||||||
catch (IOException | RuntimeException ex) {
|
catch (IOException | RuntimeException ex) {
|
||||||
zip.data.close();
|
zip.data.close();
|
||||||
|
@ -110,6 +110,26 @@ class NestedJarFileTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getManifestWhenNestedJarReturnsManifestOfNestedJar() throws Exception {
|
||||||
|
try (JarFile jar = new JarFile(this.file)) {
|
||||||
|
try (NestedJarFile nestedJar = new NestedJarFile(this.file, "nested.jar")) {
|
||||||
|
Manifest manifest = nestedJar.getManifest();
|
||||||
|
assertThat(manifest).isNotEqualTo(jar.getManifest());
|
||||||
|
assertThat(manifest.getMainAttributes().getValue("Built-By")).isEqualTo("j2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getManifestWhenNestedJarDirectoryReturnsManifestOfParent() throws Exception {
|
||||||
|
try (JarFile jar = new JarFile(this.file)) {
|
||||||
|
try (NestedJarFile nestedJar = new NestedJarFile(this.file, "d/")) {
|
||||||
|
assertThat(nestedJar.getManifest()).isEqualTo(jar.getManifest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createWhenJarHasFrontMatterOpensJar() throws IOException {
|
void createWhenJarHasFrontMatterOpensJar() throws IOException {
|
||||||
File file = new File(this.tempDir, "frontmatter.jar");
|
File file = new File(this.tempDir, "frontmatter.jar");
|
||||||
|
@ -48,6 +48,7 @@ import org.junit.jupiter.api.io.TempDir;
|
|||||||
|
|
||||||
import org.springframework.boot.loader.testsupport.TestJar;
|
import org.springframework.boot.loader.testsupport.TestJar;
|
||||||
import org.springframework.boot.loader.zip.ZipContent.Entry;
|
import org.springframework.boot.loader.zip.ZipContent.Entry;
|
||||||
|
import org.springframework.boot.loader.zip.ZipContent.Kind;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
@ -168,6 +169,25 @@ class ZipContentTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getKindWhenZipReturnsZip() {
|
||||||
|
assertThat(this.zipContent.getKind()).isEqualTo(Kind.ZIP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getKindWhenNestedZipReturnsNestedZip() throws IOException {
|
||||||
|
try (ZipContent nested = ZipContent.open(this.file.toPath(), "nested.jar")) {
|
||||||
|
assertThat(nested.getKind()).isEqualTo(Kind.NESTED_ZIP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getKindWhenNestedDirectoryReturnsNestedDirectory() throws IOException {
|
||||||
|
try (ZipContent nested = ZipContent.open(this.file.toPath(), "d/")) {
|
||||||
|
assertThat(nested.getKind()).isEqualTo(Kind.NESTED_DIRECTORY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void assertThatFieldsAreEqual(ZipEntry actual, ZipEntry expected) {
|
private void assertThatFieldsAreEqual(ZipEntry actual, ZipEntry expected) {
|
||||||
assertThat(actual.getName()).isEqualTo(expected.getName());
|
assertThat(actual.getName()).isEqualTo(expected.getName());
|
||||||
assertThat(actual.getTime()).isEqualTo(expected.getTime());
|
assertThat(actual.getTime()).isEqualTo(expected.getTime());
|
||||||
|
Loading…
Reference in New Issue
Block a user