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 @@ @@ -16,6 +16,7 @@
package org.springframework.boot.jarmode.tools;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@ -26,6 +27,7 @@ import java.io.PrintStream; @@ -26,6 +27,7 @@ import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.EnumSet;
import java.util.Enumeration;
@ -34,6 +36,7 @@ import java.util.List; @@ -34,6 +36,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
@ -242,10 +245,17 @@ class ExtractCommand extends Command { @@ -242,10 +245,17 @@ class ExtractCommand extends Command {
String librariesDirectory = getLibrariesDirectory(options);
Manifest manifest = jarStructure.createLauncherManifest((library) -> librariesDirectory + library);
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);
Set<String> writtenEntries = new HashSet<>();
withJarEntries(this.context.getArchiveFile(), ((stream, jarEntry) -> {
withJarEntries(this.context.getArchiveFile(), manfiestWriter, ((stream, jarEntry) -> {
Entry entry = jarStructure.resolve(jarEntry);
if (entry != null && allowedTypes.contains(entry.type()) && StringUtils.hasLength(entry.location())) {
JarEntry newJarEntry = createJarEntry(entry.location(), jarEntry);
@ -263,6 +273,15 @@ class ExtractCommand extends Command { @@ -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) {
@ -287,6 +306,28 @@ class ExtractCommand extends Command { @@ -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) {
return (entry.getCreationTime() != null) ? entry.getCreationTime() : entry.getLastModifiedTime();
}
@ -299,31 +340,16 @@ class ExtractCommand extends Command { @@ -299,31 +340,16 @@ class ExtractCommand extends Command {
return (entry.getLastModifiedTime() != null) ? entry.getLastModifiedTime() : entry.getCreationTime();
}
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 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 {
withJarEntries(file, null, callback);
}
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)) {
if (manfiestWriter != null) {
manfiestWriter.writeManifest(jarFile);
}
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
@ -352,6 +378,13 @@ class ExtractCommand extends Command { @@ -352,6 +378,13 @@ class ExtractCommand extends Command {
}
@FunctionalInterface
private interface ManfiestWriter {
void writeManifest(JarFile sourceJarFile) throws IOException;
}
@FunctionalInterface
private interface ThrowingConsumer {

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

@ -16,12 +16,14 @@ @@ -16,12 +16,14 @@
package org.springframework.boot.jarmode.tools;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.time.Instant;
import java.util.ArrayDeque;
@ -80,18 +82,13 @@ abstract class AbstractJarModeTests { @@ -80,18 +82,13 @@ abstract class AbstractJarModeTests {
@Nullable Instant lastAccessTime, String... entries) throws IOException {
Assert.state(entries.length % 2 == 0, "Entries must be key value pairs");
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) {
ZipEntry entry = new ZipEntry(entries[i]);
if (creationTime != null) {
entry.setCreationTime(FileTime.from(creationTime));
}
if (lastModifiedTime != null) {
entry.setLastModifiedTime(FileTime.from(lastModifiedTime));
}
if (lastAccessTime != null) {
entry.setLastAccessTime(FileTime.from(lastAccessTime));
}
ZipEntry entry = createEntry(entries[i], creationTime, lastModifiedTime, lastAccessTime);
jar.putNextEntry(entry);
String resource = entries[i + 1];
if (resource != null) {
@ -103,9 +100,30 @@ abstract class AbstractJarModeTests { @@ -103,9 +100,30 @@ abstract class AbstractJarModeTests {
jar.closeEntry();
}
}
Files.getFileAttributeView(file.toPath(), BasicFileAttributeView.class)
.setTimes(asFileTime(lastModifiedTime), asFileTime(lastAccessTime), asFileTime(creationTime));
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) {
Context context = new Context(archive, this.tempDir);
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; @@ -24,9 +24,12 @@ import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
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.Nested;
@ -79,20 +82,6 @@ class ExtractCommandTests extends AbstractJarModeTests { @@ -79,20 +82,6 @@ class ExtractCommandTests extends AbstractJarModeTests {
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
class Extract {
@ -155,10 +144,48 @@ class ExtractCommandTests extends AbstractJarModeTests { @@ -155,10 +144,48 @@ class ExtractCommandTests extends AbstractJarModeTests {
@Test
void appliesFileTimes() {
run(ExtractCommandTests.this.archive);
assertThat(file("test/lib/dependency-1.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("test/lib/dependency-2.jar")).exists().satisfies(ExtractCommandTests.this::timeAttributes);
assertThat(file("test/lib/dependency-3-SNAPSHOT.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(this::fileTimeAttributes);
assertThat(file("test/lib/dependency-3-SNAPSHOT.jar")).exists().satisfies(this::fileTimeAttributes);
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

Loading…
Cancel
Save