mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Merge branch '4.0.x'
Closes gh-48679
This commit is contained in:
+58
-25
@@ -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;
|
||||
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;
|
||||
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 {
|
||||
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 {
|
||||
}
|
||||
}));
|
||||
}
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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, @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 {
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ManfiestWriter {
|
||||
|
||||
void writeManifest(JarFile sourceJarFile) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ThrowingConsumer {
|
||||
|
||||
|
||||
+29
-11
@@ -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 {
|
||||
@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 {
|
||||
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);
|
||||
|
||||
+45
-18
@@ -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 {
|
||||
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 {
|
||||
@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
|
||||
|
||||
Reference in New Issue
Block a user