Skip to content

Commit 640793c

Browse files
committed
Streamline CRC and hash calculation when preparing stored entries
Closes gh-46202
1 parent 0932c4a commit 640793c

File tree

12 files changed

+205
-79
lines changed

12 files changed

+205
-79
lines changed

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@
2222
import java.io.InputStream;
2323
import java.io.OutputStream;
2424
import java.io.OutputStreamWriter;
25+
import java.security.MessageDigest;
26+
import java.security.NoSuchAlgorithmException;
2527
import java.time.OffsetDateTime;
2628
import java.time.ZoneOffset;
2729
import java.util.Collection;
2830
import java.util.HashMap;
31+
import java.util.HexFormat;
2932
import java.util.LinkedHashMap;
3033
import java.util.LinkedHashSet;
3134
import java.util.List;
@@ -350,7 +353,7 @@ private void writeJarModeLibrary(String location, JarModeLibrary library) throws
350353
String name = location + library.getName();
351354
writeEntry(name, ZipEntryContentWriter.fromInputStream(library.openStream()), false, (entry) -> {
352355
try (InputStream in = library.openStream()) {
353-
prepareStoredEntry(library.openStream(), entry);
356+
prepareStoredEntry(library.openStream(), false, entry);
354357
}
355358
});
356359
if (BootZipCopyAction.this.layerResolver != null) {
@@ -450,14 +453,13 @@ private void prepareEntry(ZipArchiveEntry entry, String name, Long time, int mod
450453
}
451454

452455
private void prepareStoredEntry(FileCopyDetails details, ZipArchiveEntry archiveEntry) throws IOException {
453-
prepareStoredEntry(details.open(), archiveEntry);
454-
if (BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details)) {
455-
archiveEntry.setComment("UNPACK:" + FileUtils.sha1Hash(details.getFile()));
456-
}
456+
prepareStoredEntry(details.open(), BootZipCopyAction.this.requiresUnpack.isSatisfiedBy(details),
457+
archiveEntry);
457458
}
458459

459-
private void prepareStoredEntry(InputStream input, ZipArchiveEntry archiveEntry) throws IOException {
460-
new CrcAndSize(input).setUpStoredEntry(archiveEntry);
460+
private void prepareStoredEntry(InputStream input, boolean unpack, ZipArchiveEntry archiveEntry)
461+
throws IOException {
462+
new StoredEntryPreparator(input, unpack).prepareStoredEntry(archiveEntry);
461463
}
462464

463465
private Long getTime() {
@@ -569,36 +571,55 @@ static ZipEntryContentWriter fromLines(String encoding, Collection<String> lines
569571
}
570572

571573
/**
572-
* Data holder for CRC and Size.
574+
* Prepares a {@link ZipEntry#STORED stored} {@link ZipArchiveEntry entry} with CRC
575+
* and size information. Also adds an {@code UNPACK} comment, if needed.
573576
*/
574-
private static class CrcAndSize {
577+
private static class StoredEntryPreparator {
575578

576579
private static final int BUFFER_SIZE = 32 * 1024;
577580

581+
private final MessageDigest messageDigest;
582+
578583
private final CRC32 crc = new CRC32();
579584

580585
private long size;
581586

582-
CrcAndSize(InputStream inputStream) throws IOException {
587+
StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException {
588+
this.messageDigest = (unpack) ? sha1Digest() : null;
583589
try (inputStream) {
584590
load(inputStream);
585591
}
586592
}
587593

594+
private static MessageDigest sha1Digest() {
595+
try {
596+
return MessageDigest.getInstance("SHA-1");
597+
}
598+
catch (NoSuchAlgorithmException ex) {
599+
throw new IllegalStateException(ex);
600+
}
601+
}
602+
588603
private void load(InputStream inputStream) throws IOException {
589604
byte[] buffer = new byte[BUFFER_SIZE];
590605
int bytesRead;
591606
while ((bytesRead = inputStream.read(buffer)) != -1) {
592607
this.crc.update(buffer, 0, bytesRead);
608+
if (this.messageDigest != null) {
609+
this.messageDigest.update(buffer, 0, bytesRead);
610+
}
593611
this.size += bytesRead;
594612
}
595613
}
596614

597-
void setUpStoredEntry(ZipArchiveEntry entry) {
615+
void prepareStoredEntry(ZipArchiveEntry entry) {
598616
entry.setSize(this.size);
599617
entry.setCompressedSize(this.size);
600618
entry.setCrc(this.crc.getValue());
601619
entry.setMethod(ZipEntry.STORED);
620+
if (this.messageDigest != null) {
621+
entry.setComment("UNPACK:" + HexFormat.of().formatHex(this.messageDigest.digest()));
622+
}
602623
}
603624

604625
}

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/buildinfo/BuildInfoIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import org.junit.jupiter.api.TestTemplate;
3030

3131
import org.springframework.boot.gradle.junit.GradleCompatibility;
32-
import org.springframework.boot.loader.tools.FileUtils;
32+
import org.springframework.boot.testsupport.FileUtils;
3333
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
3434

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

spring-boot-project/spring-boot-tools/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
import org.gradle.testkit.runner.TaskOutcome;
5353
import org.junit.jupiter.api.TestTemplate;
5454

55-
import org.springframework.boot.loader.tools.FileUtils;
5655
import org.springframework.boot.loader.tools.JarModeLibrary;
56+
import org.springframework.boot.testsupport.FileUtils;
5757
import org.springframework.boot.testsupport.gradle.testkit.GradleBuild;
5858
import org.springframework.util.FileSystemUtils;
5959
import org.springframework.util.StringUtils;

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

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,18 @@
1818

1919
import java.io.BufferedInputStream;
2020
import java.io.BufferedWriter;
21-
import java.io.ByteArrayInputStream;
22-
import java.io.ByteArrayOutputStream;
2321
import java.io.IOException;
2422
import java.io.InputStream;
2523
import java.io.OutputStream;
2624
import java.io.OutputStreamWriter;
2725
import java.net.URL;
2826
import java.nio.charset.StandardCharsets;
27+
import java.security.MessageDigest;
28+
import java.security.NoSuchAlgorithmException;
2929
import java.util.Collection;
3030
import java.util.Enumeration;
3131
import java.util.HashSet;
32+
import java.util.HexFormat;
3233
import java.util.Set;
3334
import java.util.function.Function;
3435
import java.util.jar.JarEntry;
@@ -40,6 +41,7 @@
4041

4142
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
4243
import org.apache.commons.compress.archivers.zip.UnixStat;
44+
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
4345

4446
/**
4547
* Abstract base class for JAR writers.
@@ -97,20 +99,21 @@ final void writeEntries(JarFile jarFile, EntryTransformer entryTransformer, Unpa
9799

98100
private void writeEntry(JarFile jarFile, EntryTransformer entryTransformer, UnpackHandler unpackHandler,
99101
JarArchiveEntry entry, Library library) throws IOException {
100-
setUpEntry(jarFile, entry);
102+
setUpEntry(jarFile, entry, unpackHandler);
101103
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
102104
EntryWriter entryWriter = new InputStreamEntryWriter(inputStream);
103105
JarArchiveEntry transformedEntry = entryTransformer.transform(entry);
104106
if (transformedEntry != null) {
105-
writeEntry(transformedEntry, library, entryWriter, unpackHandler);
107+
writeEntry(transformedEntry, library, entryWriter);
106108
}
107109
}
108110
}
109111

110-
private void setUpEntry(JarFile jarFile, JarArchiveEntry entry) throws IOException {
112+
private void setUpEntry(JarFile jarFile, JarArchiveEntry entry, UnpackHandler unpackHandler) throws IOException {
111113
try (ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(jarFile.getInputStream(entry))) {
112114
if (inputStream.hasZipHeader() && entry.getMethod() != ZipEntry.STORED) {
113-
new CrcAndSize(inputStream).setupStoredEntry(entry);
115+
new StoredEntryPreparator(inputStream, unpackHandler.requiresUnpack(entry.getName()))
116+
.prepareStoredEntry(entry);
114117
}
115118
else {
116119
entry.setCompressedSize(-1);
@@ -151,9 +154,10 @@ public void writeEntry(String entryName, EntryWriter entryWriter) throws IOExcep
151154
public void writeNestedLibrary(String location, Library library) throws IOException {
152155
JarArchiveEntry entry = new JarArchiveEntry(location + library.getName());
153156
entry.setTime(getNestedLibraryTime(library));
154-
new CrcAndSize(library::openStream).setupStoredEntry(entry);
157+
new StoredEntryPreparator(library.openStream(), new LibraryUnpackHandler(library).requiresUnpack(location))
158+
.prepareStoredEntry(entry);
155159
try (InputStream inputStream = library.openStream()) {
156-
writeEntry(entry, library, new InputStreamEntryWriter(inputStream), new LibraryUnpackHandler(library));
160+
writeEntry(entry, library, new InputStreamEntryWriter(inputStream));
157161
}
158162
}
159163

@@ -240,7 +244,7 @@ private boolean isServicesEntry(JarEntry entry) {
240244
}
241245

242246
private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException {
243-
writeEntry(entry, null, entryWriter, UnpackHandler.NEVER);
247+
writeEntry(entry, null, entryWriter);
244248
}
245249

246250
/**
@@ -249,11 +253,9 @@ private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws I
249253
* @param entry the entry to write
250254
* @param library the library for the entry or {@code null}
251255
* @param entryWriter the entry writer or {@code null} if there is no content
252-
* @param unpackHandler handles possible unpacking for the entry
253256
* @throws IOException in case of I/O errors
254257
*/
255-
private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter,
256-
UnpackHandler unpackHandler) throws IOException {
258+
private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entryWriter) throws IOException {
257259
String name = entry.getName();
258260
if (this.writtenEntries.add(name)) {
259261
writeParentDirectoryEntries(name);
@@ -263,7 +265,6 @@ private void writeEntry(JarArchiveEntry entry, Library library, EntryWriter entr
263265
entryWriter = SizeCalculatingEntryWriter.get(entryWriter);
264266
entry.setSize(entryWriter.size());
265267
}
266-
entryWriter = addUnpackCommentIfNecessary(entry, entryWriter, unpackHandler);
267268
updateLayerIndex(entry, library);
268269
writeToArchive(entry, entryWriter);
269270
}
@@ -283,22 +284,11 @@ private void writeParentDirectoryEntries(String name) throws IOException {
283284
while (parent.lastIndexOf('/') != -1) {
284285
parent = parent.substring(0, parent.lastIndexOf('/'));
285286
if (!parent.isEmpty()) {
286-
writeEntry(new JarArchiveEntry(parent + "/"), null, null, UnpackHandler.NEVER);
287+
writeEntry(new JarArchiveEntry(parent + "/"), null, null);
287288
}
288289
}
289290
}
290291

291-
private EntryWriter addUnpackCommentIfNecessary(JarArchiveEntry entry, EntryWriter entryWriter,
292-
UnpackHandler unpackHandler) throws IOException {
293-
if (entryWriter == null || !unpackHandler.requiresUnpack(entry.getName())) {
294-
return entryWriter;
295-
}
296-
ByteArrayOutputStream output = new ByteArrayOutputStream();
297-
entryWriter.write(output);
298-
entry.setComment("UNPACK:" + unpackHandler.sha1Hash(entry.getName()));
299-
return new InputStreamEntryWriter(new ByteArrayInputStream(output.toByteArray()));
300-
}
301-
302292
/**
303293
* {@link EntryWriter} that writes content from an {@link InputStream}.
304294
*/
@@ -323,38 +313,55 @@ public void write(OutputStream outputStream) throws IOException {
323313
}
324314

325315
/**
326-
* Data holder for CRC and Size.
316+
* Prepares a {@link ZipEntry#STORED stored} {@link ZipArchiveEntry entry} with CRC
317+
* and size information. Also adds an {@code UNPACK} comment, if needed.
327318
*/
328-
private static class CrcAndSize {
319+
private static class StoredEntryPreparator {
320+
321+
private static final int BUFFER_SIZE = 32 * 1024;
322+
323+
private final MessageDigest messageDigest;
329324

330325
private final CRC32 crc = new CRC32();
331326

332327
private long size;
333328

334-
CrcAndSize(InputStreamSupplier supplier) throws IOException {
335-
try (InputStream inputStream = supplier.openStream()) {
329+
StoredEntryPreparator(InputStream inputStream, boolean unpack) throws IOException {
330+
this.messageDigest = (unpack) ? sha1Digest() : null;
331+
try (inputStream) {
336332
load(inputStream);
337333
}
338334
}
339335

340-
CrcAndSize(InputStream inputStream) throws IOException {
341-
load(inputStream);
336+
private static MessageDigest sha1Digest() {
337+
try {
338+
return MessageDigest.getInstance("SHA-1");
339+
}
340+
catch (NoSuchAlgorithmException ex) {
341+
throw new IllegalStateException(ex);
342+
}
342343
}
343344

344345
private void load(InputStream inputStream) throws IOException {
345346
byte[] buffer = new byte[BUFFER_SIZE];
346347
int bytesRead;
347348
while ((bytesRead = inputStream.read(buffer)) != -1) {
348349
this.crc.update(buffer, 0, bytesRead);
350+
if (this.messageDigest != null) {
351+
this.messageDigest.update(buffer, 0, bytesRead);
352+
}
349353
this.size += bytesRead;
350354
}
351355
}
352356

353-
void setupStoredEntry(JarArchiveEntry entry) {
357+
void prepareStoredEntry(ZipArchiveEntry entry) {
354358
entry.setSize(this.size);
355359
entry.setCompressedSize(this.size);
356360
entry.setCrc(this.crc.getValue());
357361
entry.setMethod(ZipEntry.STORED);
362+
if (this.messageDigest != null) {
363+
entry.setComment("UNPACK:" + HexFormat.of().formatHex(this.messageDigest.digest()));
364+
}
358365
}
359366

360367
}
@@ -381,24 +388,10 @@ interface EntryTransformer {
381388
*/
382389
interface UnpackHandler {
383390

384-
UnpackHandler NEVER = new UnpackHandler() {
385-
386-
@Override
387-
public boolean requiresUnpack(String name) {
388-
return false;
389-
}
390-
391-
@Override
392-
public String sha1Hash(String name) throws IOException {
393-
throw new UnsupportedOperationException();
394-
}
395-
396-
};
391+
UnpackHandler NEVER = (name) -> false;
397392

398393
boolean requiresUnpack(String name);
399394

400-
String sha1Hash(String name) throws IOException;
401-
402395
}
403396

404397
/**
@@ -417,11 +410,6 @@ public boolean requiresUnpack(String name) {
417410
return this.library.isUnpackRequired();
418411
}
419412

420-
@Override
421-
public String sha1Hash(String name) throws IOException {
422-
return Digest.sha1(this.library::openStream);
423-
}
424-
425413
}
426414

427415
}

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package org.springframework.boot.loader.tools;
1818

1919
import java.io.IOException;
20-
import java.security.DigestInputStream;
20+
import java.io.InputStream;
2121
import java.security.MessageDigest;
2222
import java.security.NoSuchAlgorithmException;
2323
import java.util.HexFormat;
@@ -26,9 +26,14 @@
2626
* Utility class used to calculate digests.
2727
*
2828
* @author Phillip Webb
29+
* @author Andy Wilkinson
30+
* @deprecated since 3.4.8 for removal in 4.0.0 without replacement
2931
*/
32+
@Deprecated(since = "3.4.8", forRemoval = true)
3033
final class Digest {
3134

35+
private static final int BUFFER_SIZE = 32 * 1024;
36+
3237
private Digest() {
3338
}
3439

@@ -40,10 +45,14 @@ private Digest() {
4045
*/
4146
static String sha1(InputStreamSupplier supplier) throws IOException {
4247
try {
43-
try (DigestInputStream inputStream = new DigestInputStream(supplier.openStream(),
44-
MessageDigest.getInstance("SHA-1"))) {
45-
inputStream.readAllBytes();
46-
return HexFormat.of().formatHex(inputStream.getMessageDigest().digest());
48+
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
49+
try (InputStream inputStream = supplier.openStream()) {
50+
byte[] buffer = new byte[BUFFER_SIZE];
51+
int bytesRead;
52+
while ((bytesRead = inputStream.read(buffer)) != -1) {
53+
messageDigest.update(buffer, 0, bytesRead);
54+
}
55+
return HexFormat.of().formatHex(messageDigest.digest());
4756
}
4857
}
4958
catch (NoSuchAlgorithmException ex) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ public static void removeDuplicatesFromOutputDirectory(File outputDirectory, Fil
6060
* @param file the file to hash
6161
* @return the hash value as a String
6262
* @throws IOException if the file cannot be read
63+
* @deprecated since 3.4.8 for removal in 4.0.0 without replacement
6364
*/
65+
@Deprecated(since = "3.4.8", forRemoval = true)
66+
@SuppressWarnings("removal")
6467
public static String sha1Hash(File file) throws IOException {
6568
return Digest.sha1(InputStreamSupplier.forFile(file));
6669
}

0 commit comments

Comments
 (0)