Browse Source

Merge branch '4.0.x'

Closes gh-48679
pull/48680/head
Phillip Webb 4 weeks ago
parent
commit
a6e4a93cda
  1. 81
      loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java
  2. 40
      loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/AbstractJarModeTests.java
  3. 63
      loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java

81
loader/spring-boot-jarmode-tools/src/main/java/org/springframework/boot/jarmode/tools/ExtractCommand.java

@ -16,6 +16,7 @@
package org.springframework.boot.jarmode.tools; package org.springframework.boot.jarmode.tools;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -26,6 +27,7 @@ import java.io.PrintStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributeView; import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Enumeration; import java.util.Enumeration;
@ -34,6 +36,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
@ -242,10 +245,17 @@ class ExtractCommand extends Command {
String librariesDirectory = getLibrariesDirectory(options); String librariesDirectory = getLibrariesDirectory(options);
Manifest manifest = jarStructure.createLauncherManifest((library) -> librariesDirectory + library); Manifest manifest = jarStructure.createLauncherManifest((library) -> librariesDirectory + library);
mkdirs(file.getParentFile()); mkdirs(file.getParentFile());
try (JarOutputStream output = new JarOutputStream(new FileOutputStream(file), manifest)) { try (JarOutputStream output = new JarOutputStream(new FileOutputStream(file))) {
ManfiestWriter manfiestWriter = (sourceJarFile) -> {
JarEntry entry = createJarEntry(JarFile.MANIFEST_NAME,
sourceJarFile.getJarEntry(JarFile.MANIFEST_NAME));
output.putNextEntry(entry);
manifest.write(new BufferedOutputStream(output));
output.closeEntry();
};
EnumSet<Type> allowedTypes = EnumSet.of(Type.APPLICATION_CLASS_OR_RESOURCE, Type.META_INF); EnumSet<Type> allowedTypes = EnumSet.of(Type.APPLICATION_CLASS_OR_RESOURCE, Type.META_INF);
Set<String> writtenEntries = new HashSet<>(); Set<String> writtenEntries = new HashSet<>();
withJarEntries(this.context.getArchiveFile(), ((stream, jarEntry) -> { withJarEntries(this.context.getArchiveFile(), manfiestWriter, ((stream, jarEntry) -> {
Entry entry = jarStructure.resolve(jarEntry); Entry entry = jarStructure.resolve(jarEntry);
if (entry != null && allowedTypes.contains(entry.type()) && StringUtils.hasLength(entry.location())) { if (entry != null && allowedTypes.contains(entry.type()) && StringUtils.hasLength(entry.location())) {
JarEntry newJarEntry = createJarEntry(entry.location(), jarEntry); JarEntry newJarEntry = createJarEntry(entry.location(), jarEntry);
@ -263,6 +273,15 @@ class ExtractCommand extends Command {
} }
})); }));
} }
copyTimestamps(this.context.getArchiveFile(), file);
}
private void copyTimestamps(File source, File destination) throws IOException {
BasicFileAttributes sourceAttributes = Files.getFileAttributeView(source.toPath(), BasicFileAttributeView.class)
.readAttributes();
Files.getFileAttributeView(destination.toPath(), BasicFileAttributeView.class)
.setTimes(sourceAttributes.lastModifiedTime(), sourceAttributes.lastAccessTime(),
sourceAttributes.creationTime());
} }
private String getApplicationFilename(Map<Option, @Nullable String> options) { private String getApplicationFilename(Map<Option, @Nullable String> options) {
@ -287,6 +306,28 @@ class ExtractCommand extends Command {
} }
} }
private static void mkdirs(File file) throws IOException {
if (!file.exists() && !file.mkdirs()) {
throw new IOException("Unable to create directory " + file);
}
}
private static JarEntry createJarEntry(String location, JarEntry originalEntry) {
JarEntry entry = new JarEntry(location);
if (originalEntry != null) {
copyFileTime(getLastModifiedTime(originalEntry), entry::setLastModifiedTime);
copyFileTime(getLastAccessTime(originalEntry), entry::setLastAccessTime);
copyFileTime(getCreationTime(originalEntry), entry::setCreationTime);
}
return entry;
}
private static void copyFileTime(@Nullable FileTime fileTime, Consumer<FileTime> setter) {
if (fileTime != null) {
setter.accept(fileTime);
}
}
private static @Nullable FileTime getCreationTime(JarEntry entry) { private static @Nullable FileTime getCreationTime(JarEntry entry) {
return (entry.getCreationTime() != null) ? entry.getCreationTime() : entry.getLastModifiedTime(); return (entry.getCreationTime() != null) ? entry.getCreationTime() : entry.getLastModifiedTime();
} }
@ -299,31 +340,16 @@ class ExtractCommand extends Command {
return (entry.getLastModifiedTime() != null) ? entry.getLastModifiedTime() : entry.getCreationTime(); return (entry.getLastModifiedTime() != null) ? entry.getLastModifiedTime() : entry.getCreationTime();
} }
private static void mkdirs(File file) throws IOException { private static void withJarEntries(File file, ThrowingConsumer callback) throws IOException {
if (!file.exists() && !file.mkdirs()) { withJarEntries(file, null, callback);
throw new IOException("Unable to create directory " + file);
}
}
private static JarEntry createJarEntry(String location, JarEntry originalEntry) {
JarEntry jarEntry = new JarEntry(location);
FileTime lastModifiedTime = getLastModifiedTime(originalEntry);
if (lastModifiedTime != null) {
jarEntry.setLastModifiedTime(lastModifiedTime);
}
FileTime lastAccessTime = getLastAccessTime(originalEntry);
if (lastAccessTime != null) {
jarEntry.setLastAccessTime(lastAccessTime);
}
FileTime creationTime = getCreationTime(originalEntry);
if (creationTime != null) {
jarEntry.setCreationTime(creationTime);
}
return jarEntry;
} }
private static void withJarEntries(File file, ThrowingConsumer callback) throws IOException { private static void withJarEntries(File file, @Nullable ManfiestWriter manfiestWriter, ThrowingConsumer callback)
throws IOException {
try (JarFile jarFile = new JarFile(file)) { try (JarFile jarFile = new JarFile(file)) {
if (manfiestWriter != null) {
manfiestWriter.writeManifest(jarFile);
}
Enumeration<JarEntry> entries = jarFile.entries(); Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) { while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement(); JarEntry entry = entries.nextElement();
@ -352,6 +378,13 @@ class ExtractCommand extends Command {
} }
@FunctionalInterface
private interface ManfiestWriter {
void writeManifest(JarFile sourceJarFile) throws IOException;
}
@FunctionalInterface @FunctionalInterface
private interface ThrowingConsumer { private interface ThrowingConsumer {

40
loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/AbstractJarModeTests.java

@ -16,12 +16,14 @@
package org.springframework.boot.jarmode.tools; package org.springframework.boot.jarmode.tools;
import java.io.BufferedOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -80,18 +82,13 @@ abstract class AbstractJarModeTests {
@Nullable Instant lastAccessTime, String... entries) throws IOException { @Nullable Instant lastAccessTime, String... entries) throws IOException {
Assert.state(entries.length % 2 == 0, "Entries must be key value pairs"); Assert.state(entries.length % 2 == 0, "Entries must be key value pairs");
File file = new File(this.tempDir, "test.jar"); File file = new File(this.tempDir, "test.jar");
try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file), manifest)) { try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(file))) {
ZipEntry manifestEntry = createEntry(JarFile.MANIFEST_NAME, creationTime, lastModifiedTime, lastAccessTime);
jar.putNextEntry(manifestEntry);
manifest.write(new BufferedOutputStream(jar));
jar.closeEntry();
for (int i = 0; i < entries.length; i += 2) { for (int i = 0; i < entries.length; i += 2) {
ZipEntry entry = new ZipEntry(entries[i]); ZipEntry entry = createEntry(entries[i], creationTime, lastModifiedTime, lastAccessTime);
if (creationTime != null) {
entry.setCreationTime(FileTime.from(creationTime));
}
if (lastModifiedTime != null) {
entry.setLastModifiedTime(FileTime.from(lastModifiedTime));
}
if (lastAccessTime != null) {
entry.setLastAccessTime(FileTime.from(lastAccessTime));
}
jar.putNextEntry(entry); jar.putNextEntry(entry);
String resource = entries[i + 1]; String resource = entries[i + 1];
if (resource != null) { if (resource != null) {
@ -103,9 +100,30 @@ abstract class AbstractJarModeTests {
jar.closeEntry(); jar.closeEntry();
} }
} }
Files.getFileAttributeView(file.toPath(), BasicFileAttributeView.class)
.setTimes(asFileTime(lastModifiedTime), asFileTime(lastAccessTime), asFileTime(creationTime));
return file; return file;
} }
private @Nullable FileTime asFileTime(@Nullable Instant instant) {
return (instant != null) ? FileTime.from(instant) : null;
}
private ZipEntry createEntry(String name, @Nullable Instant creationTime, @Nullable Instant lastModifiedTime,
@Nullable Instant lastAccessTime) {
ZipEntry entry = new ZipEntry(name);
if (creationTime != null) {
entry.setCreationTime(FileTime.from(creationTime));
}
if (lastModifiedTime != null) {
entry.setLastModifiedTime(FileTime.from(lastModifiedTime));
}
if (lastAccessTime != null) {
entry.setLastAccessTime(FileTime.from(lastAccessTime));
}
return entry;
}
TestPrintStream runCommand(CommandFactory<?> commandFactory, File archive, String... arguments) { TestPrintStream runCommand(CommandFactory<?> commandFactory, File archive, String... arguments) {
Context context = new Context(archive, this.tempDir); Context context = new Context(archive, this.tempDir);
Command command = commandFactory.create(context); Command command = commandFactory.create(context);

63
loader/spring-boot-jarmode-tools/src/test/java/org/springframework/boot/jarmode/tools/ExtractCommandTests.java

@ -24,9 +24,12 @@ import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant; import java.time.Instant;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.jar.Manifest; import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Nested;
@ -79,20 +82,6 @@ class ExtractCommandTests extends AbstractJarModeTests {
return runCommand(ExtractCommand::new, archive, args); return runCommand(ExtractCommand::new, archive, args);
} }
private void timeAttributes(File file) {
try {
BasicFileAttributes basicAttributes = Files
.getFileAttributeView(file.toPath(), BasicFileAttributeView.class)
.readAttributes();
assertThat(basicAttributes.lastModifiedTime().toInstant().truncatedTo(ChronoUnit.SECONDS))
.as("last modified time")
.isEqualTo(LAST_MODIFIED_TIME.truncatedTo(ChronoUnit.SECONDS));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
@Nested @Nested
class Extract { class Extract {
@ -155,10 +144,48 @@ class ExtractCommandTests extends AbstractJarModeTests {
@Test @Test
void appliesFileTimes() { void appliesFileTimes() {
run(ExtractCommandTests.this.archive); run(ExtractCommandTests.this.archive);
assertThat(file("test/lib/dependency-1.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes); assertThat(file("test/lib/dependency-1.jar")).exists().satisfies(this::fileTimeAttributes);
assertThat(file("test/lib/dependency-2.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes); assertThat(file("test/lib/dependency-2.jar")).exists().satisfies(this::fileTimeAttributes);
assertThat(file("test/lib/dependency-3-SNAPSHOT.jar")).exists() assertThat(file("test/lib/dependency-3-SNAPSHOT.jar")).exists().satisfies(this::fileTimeAttributes);
.satisfies(ExtractCommandTests.this::timeAttributes); assertThat(file("test/test.jar")).exists()
.satisfies(this::fileTimeAttributes)
.satisfies(this::entryTimeAttributes);
}
private void fileTimeAttributes(File file) {
try {
BasicFileAttributes basicAttributes = Files
.getFileAttributeView(file.toPath(), BasicFileAttributeView.class)
.readAttributes();
assertThat(basicAttributes.lastModifiedTime().toInstant().truncatedTo(ChronoUnit.SECONDS))
.as("last modified time")
.isEqualTo(LAST_MODIFIED_TIME.truncatedTo(ChronoUnit.SECONDS));
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void entryTimeAttributes(File file) {
try {
try (ZipFile archiveZipFile = new ZipFile(ExtractCommandTests.this.archive)) {
try (ZipFile zipFile = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
ZipEntry archiveEntry = archiveZipFile.getEntry(entry.getName());
if (archiveEntry != null) {
assertThat(entry.getLastModifiedTime()).isEqualTo(archiveEntry.getLastModifiedTime());
assertThat(entry.getLastAccessTime()).isEqualTo(archiveEntry.getLastAccessTime());
assertThat(entry.getCreationTime()).isEqualTo(archiveEntry.getCreationTime());
}
}
}
}
}
catch (IOException ex) {
throw new RuntimeException(ex);
}
} }
@Test @Test

Loading…
Cancel
Save