From b42f056ddbfd5041ef80d2d909dd2f5e51ec3ff0 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 14 Jun 2022 20:46:51 -0700 Subject: [PATCH] Don't close jar files early Update `JarFile` and related classes so that `close()` is not longer called early. Prior to this commit, we would always immediately close the underlying jar file to prevent file locking issues with our build. This causes issues on certain JVMs when they attempt to verify a signed jar. The file lock issues have now been solved by returning a custom input stream from `JarUrlConnection` which captures and delegates the close method. Fixes gh-29356 --- .../boot/loader/jar/JarFile.java | 42 +++++++++++++------ .../boot/loader/jar/JarFileWrapper.java | 5 +-- .../boot/loader/jar/JarURLConnection.java | 18 +++++++- .../boot/loader/jar/JarFileWrapperTests.java | 3 +- .../spring-boot-loader-tests/build.gradle | 16 ++++++- .../build.gradle | 22 ++++++++++ .../settings.gradle | 15 +++++++ .../LoaderSignedJarTestApplication.java | 36 ++++++++++++++++ .../boot/loader/LoaderIntegrationTests.java | 29 +++++++++---- 9 files changed, 158 insertions(+), 28 deletions(-) create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-signed-jar-unpack-app/build.gradle create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-signed-jar-unpack-app/settings.gradle create mode 100644 spring-boot-tests/spring-boot-integration-tests/spring-boot-loader-tests/spring-boot-loader-tests-signed-jar-unpack-app/src/main/java/org/springframework/boot/loaderapp/LoaderSignedJarTestApplication.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java index 81386386f93..65727df35d0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors. + * Copyright 2012-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,11 @@ import java.net.URL; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.security.Permission; +import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; +import java.util.List; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Supplier; @@ -93,6 +96,8 @@ public class JarFile extends AbstractJarFile implements Iterable nestedJars = Collections.synchronizedList(new ArrayList<>()); + /** * Create a new {@link JarFile} backed by the specified file. * @param file the root jar file @@ -128,9 +133,6 @@ public class JarFile extends AbstractJarFile implements Iterable manifestSupplier) throws IOException { super(rootFile.getFile()); - if (System.getSecurityManager() == null) { - super.close(); - } this.rootFile = rootFile; this.pathFromRoot = pathFromRoot; CentralDirectoryParser parser = new CentralDirectoryParser(); @@ -142,8 +144,7 @@ public class JarFile extends AbstractJarFile implements Iterable container = createContainer(javaRuntime)) { + try (GenericContainer container = createContainer(javaRuntime, "spring-boot-loader-tests-app")) { container.start(); System.out.println(this.output.toUtf8String()); assertThat(this.output.toUtf8String()).contains(">>>>> 287649 BYTES from").doesNotContain("WARNING:") @@ -59,17 +59,32 @@ class LoaderIntegrationTests { } } - private GenericContainer createContainer(JavaRuntime javaRuntime) { + @ParameterizedTest + @MethodSource("javaRuntimes") + void runSignedJarWhenUnpacked(JavaRuntime javaRuntime) { + try (GenericContainer container = createContainer(javaRuntime, + "spring-boot-loader-tests-signed-jar-unpack-app")) { + container.start(); + System.out.println(this.output.toUtf8String()); + assertThat(this.output.toUtf8String()).contains("Legion of the Bouncy Castle"); + } + } + + private GenericContainer createContainer(JavaRuntime javaRuntime, String name) { return javaRuntime.getContainer().withLogConsumer(this.output) - .withCopyFileToContainer(MountableFile.forHostPath(findApplication().toPath()), "/app.jar") + .withCopyFileToContainer(findApplication(name), "/app.jar") .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5))) .withCommand("java", "-jar", "app.jar"); } - private File findApplication() { - String name = String.format("build/%1$s/build/libs/%1$s.jar", "spring-boot-loader-tests-app"); - File jar = new File(name); - Assert.state(jar.isFile(), () -> "Could not find " + name + ". Have you built it?"); + private MountableFile findApplication(String name) { + return MountableFile.forHostPath(findJarFile(name).toPath()); + } + + private File findJarFile(String name) { + String path = String.format("build/%1$s/build/libs/%1$s.jar", name); + File jar = new File(path); + Assert.state(jar.isFile(), () -> "Could not find " + path + ". Have you built it?"); return jar; }