From fa186aa15b7aead1a309c603862862bf9ed32d06 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Tue, 21 Apr 2020 18:58:34 -0500 Subject: [PATCH] Preserve timestamps on loader directories Prior to this commit, when the Maven plugin copied spring-boot-loader.jar to a repackaged archive the timestamps of class files were preserved but the timestamps of directories were not preserved. This resulted in the directories having a current timestamp. This commit copies the directory timestamps from spring-boot-loader.jar to the repackaged archive and adds tests to verify the proper behavior. See gh-20927 --- .../boot/loader/tools/AbstractJarWriter.java | 10 +++++- .../boot/loader/tools/RepackagerTests.java | 33 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java index d5cdcdda26..3cbf908346 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java @@ -209,13 +209,21 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { try (JarInputStream inputStream = new JarInputStream(new BufferedInputStream(loaderJar.openStream()))) { JarEntry entry; while ((entry = inputStream.getNextJarEntry()) != null) { - if (entry.getName().endsWith(".class")) { + if (isDirectoryEntry(entry) || isClassEntry(entry)) { writeEntry(new JarArchiveEntry(entry), new InputStreamEntryWriter(inputStream)); } } } } + private boolean isDirectoryEntry(JarEntry entry) { + return entry.isDirectory() && !entry.getName().equals("META-INF/"); + } + + private boolean isClassEntry(JarEntry entry) { + return entry.getName().endsWith(".class"); + } + private void writeEntry(JarArchiveEntry entry, EntryWriter entryWriter) throws IOException { writeEntry(entry, entryWriter, UnpackHandler.NEVER); } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java index c92e043148..1e632563c8 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/test/java/org/springframework/boot/loader/tools/RepackagerTests.java @@ -20,7 +20,10 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFilePermission; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; @@ -156,6 +159,36 @@ class RepackagerTests extends AbstractPackagerTests { } } + @Test + void allLoaderDirectoriesAndFilesUseSameTimestamp() throws IOException { + this.testJarFile.addClass("A.class", ClassWithMainMethod.class); + Repackager repackager = createRepackager(this.testJarFile.getFile(), true); + Long timestamp = null; + repackager.repackage(this.destination, NO_LIBRARIES); + for (ZipArchiveEntry entry : getAllPackagedEntries()) { + if (entry.getName().startsWith("org/springframework/boot/loader")) { + if (timestamp == null) { + timestamp = entry.getTime(); + } + else { + assertThat(entry.getTime()).withFailMessage("Expected time %d to be equal to %d for entry %s", + entry.getTime(), timestamp, entry.getName()).isEqualTo(timestamp); + } + } + } + } + + @Test + void allEntriesUseProvidedTimestamp() throws IOException { + this.testJarFile.addClass("A.class", ClassWithMainMethod.class); + Repackager repackager = createRepackager(this.testJarFile.getFile(), true); + long timestamp = OffsetDateTime.of(2000, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant().toEpochMilli(); + repackager.repackage(this.destination, NO_LIBRARIES, null, FileTime.fromMillis(timestamp)); + for (ZipArchiveEntry entry : getAllPackagedEntries()) { + assertThat(entry.getTime()).isEqualTo(timestamp); + } + } + private boolean hasLauncherClasses(File file) throws IOException { return hasEntry(file, "org/springframework/boot/") && hasEntry(file, "org/springframework/boot/loader/JarLauncher.class");