From bc46bb2a24f430f78b5b8eec6d41b5304bf8c811 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Tue, 23 Sep 2025 14:21:02 -0700 Subject: [PATCH] Drop spring-boot-loader-classic support Closes gh-45714 --- .../gradle/tasks/bundling/BootArchive.java | 11 - .../tasks/bundling/BootArchiveSupport.java | 13 +- .../boot/gradle/tasks/bundling/BootJar.java | 5 +- .../boot/gradle/tasks/bundling/BootWar.java | 7 +- .../tasks/bundling/BootZipCopyAction.java | 9 +- .../tasks/bundling/LoaderZipEntries.java | 10 +- .../AbstractBootArchiveIntegrationTests.java | 10 - .../bundling/AbstractBootArchiveTests.java | 12 - ...otJarIntegrationTests-classicLoader.gradle | 25 - ...otWarIntegrationTests-classicLoader.gradle | 25 - .../boot/maven/JarIntegrationTests.java | 20 - .../projects/jar-with-classic-loader/pom.xml | 60 -- .../main/java/org/test/SampleApplication.java | 24 - .../boot/maven/AbstractPackagerMojo.java | 11 - .../boot/maven/BuildImageMojo.java | 13 - .../boot/maven/RepackageMojo.java | 13 - documentation/spring-boot-docs/build.gradle | 1 - .../antora/modules/ROOT/pages/redirect.adoc | 3 - eclipse/spring-boot-project.setup | 2 +- .../build.gradle | 58 -- .../build.gradle | 41 - .../settings.gradle | 31 - .../boot/loaderapp/LoaderTestApplication.java | 59 -- .../boot/loader/LoaderIntegrationTests.java | 149 ---- .../resources/conf/oracle-jdk-17/Dockerfile | 8 - .../conf/oracle-jdk-17/Dockerfile-aarch64 | 8 - .../resources/conf/oracle-jdk-17/README.adoc | 5 - .../src/dockerTest/resources/logback.xml | 4 - .../spring-boot-loader-classic/build.gradle | 42 - .../boot/loader/ClassPathIndexFile.java | 123 --- .../loader/ExecutableArchiveLauncher.java | 207 ----- .../boot/loader/JarLauncher.java | 68 -- .../boot/loader/LaunchedURLClassLoader.java | 367 --------- .../springframework/boot/loader/Launcher.java | 159 ---- .../boot/loader/MainMethodRunner.java | 52 -- .../boot/loader/PropertiesLauncher.java | 726 ----------------- .../boot/loader/WarLauncher.java | 62 -- .../boot/loader/archive/Archive.java | 115 --- .../boot/loader/archive/ExplodedArchive.java | 342 -------- .../boot/loader/archive/JarFileArchive.java | 311 ------- .../boot/loader/archive/package-info.java | 23 - .../boot/loader/data/RandomAccessData.java | 74 -- .../loader/data/RandomAccessDataFile.java | 262 ------ .../boot/loader/data/package-info.java | 22 - .../boot/loader/jar/AbstractJarFile.java | 78 -- .../boot/loader/jar/AsciiBytes.java | 255 ------ .../boot/loader/jar/Bytes.java | 37 - .../loader/jar/CentralDirectoryEndRecord.java | 258 ------ .../jar/CentralDirectoryFileHeader.java | 222 ----- .../loader/jar/CentralDirectoryParser.java | 101 --- .../loader/jar/CentralDirectoryVisitor.java | 34 - .../boot/loader/jar/FileHeader.java | 64 -- .../boot/loader/jar/Handler.java | 470 ----------- .../boot/loader/jar/JarEntriesStream.java | 125 --- .../boot/loader/jar/JarEntry.java | 120 --- .../loader/jar/JarEntryCertification.java | 58 -- .../boot/loader/jar/JarEntryFilter.java | 35 - .../boot/loader/jar/JarFile.java | 476 ----------- .../boot/loader/jar/JarFileEntries.java | 483 ----------- .../boot/loader/jar/JarFileWrapper.java | 126 --- .../boot/loader/jar/JarURLConnection.java | 388 --------- .../boot/loader/jar/StringSequence.java | 158 ---- .../loader/jar/ZipInflaterInputStream.java | 101 --- .../boot/loader/jar/package-info.java | 20 - .../boot/loader/jarmode/JarMode.java | 43 - .../loader/jarmode/JarModeErrorException.java | 36 - .../boot/loader/jarmode/JarModeLauncher.java | 80 -- .../boot/loader/jarmode/TestJarMode.java | 44 - .../boot/loader/jarmode/package-info.java | 22 - .../boot/loader/launch/JarLauncher.java | 34 - .../loader/launch/PropertiesLauncher.java | 34 - .../boot/loader/launch/WarLauncher.java | 34 - .../boot/loader/launch/package-info.java | 23 - .../boot/loader/package-info.java | 26 - .../boot/loader/util/SystemPropertyUtils.java | 232 ------ .../boot/loader/util/package-info.java | 20 - ...bstractExecutableArchiveLauncherTests.java | 149 ---- .../boot/loader/ClassPathIndexFileTests.java | 109 --- .../boot/loader/JarLauncherTests.java | 154 ---- .../loader/LaunchedURLClassLoaderTests.java | 111 --- .../boot/loader/PropertiesLauncherTests.java | 433 ---------- .../boot/loader/TestJarCreator.java | 151 ---- .../boot/loader/WarLauncherTests.java | 121 --- .../loader/archive/ExplodedArchiveTests.java | 189 ----- .../loader/archive/JarFileArchiveTests.java | 207 ----- .../data/RandomAccessDataFileTests.java | 300 ------- .../boot/loader/jar/AsciiBytesTests.java | 196 ----- .../jar/CentralDirectoryParserTests.java | 139 ---- .../boot/loader/jar/HandlerTests.java | 210 ----- .../boot/loader/jar/JarFileTests.java | 756 ------------------ .../boot/loader/jar/JarFileWrapperTests.java | 281 ------- .../loader/jar/JarURLConnectionTests.java | 246 ------ .../loader/jar/JarUrlProtocolHandler.java | 57 -- .../boot/loader/jar/StringSequenceTests.java | 220 ----- .../loader/jarmode/LauncherJarModeTests.java | 106 --- .../loader/util/SystemPropertyUtilsTests.java | 62 -- .../BOOT-INF/classes/application.properties | 1 - .../resources/BOOT-INF/classes/bar.properties | 1 - .../resources/BOOT-INF/classes/foo.properties | 3 - .../BOOT-INF/classes/loader.properties | 1 - .../test/resources/META-INF/spring.factories | 3 - .../src/test/resources/bar.properties | 1 - .../resources/explodedsample/ExampleClass.txt | 26 - .../src/test/resources/home/loader.properties | 1 - .../src/test/resources/jars/app.jar | Bin 2213 -> 0 bytes .../src/test/resources/jars/mismatch.jar | Bin 4953 -> 0 bytes .../src/test/resources/more-jars/app.jar | Bin 1150 -> 0 bytes .../src/test/resources/nested-jars/app.jar | Bin 3313 -> 0 bytes .../resources/nested-jars/nested-jar-app.jar | Bin 1408 -> 0 bytes .../boot/loader/classpath-index-file.idx | 5 - .../placeholders/META-INF/MANIFEST.MF | 2 - .../resources/placeholders/loader.properties | 1 - .../test/resources/root/META-INF/MANIFEST.MF | 1 - .../root/META-INF/spring/application.xml | 6 - loader/spring-boot-loader-tools/build.gradle | 22 +- .../boot/loader/tools/AbstractJarWriter.java | 8 +- .../loader/tools/LoaderClassesWriter.java | 8 - .../loader/tools/LoaderImplementation.java | 51 -- .../boot/loader/tools/Packager.java | 12 +- .../spring-boot-dependencies/build.gradle | 2 - settings.gradle | 2 - 121 files changed, 15 insertions(+), 12168 deletions(-) delete mode 100644 build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle delete mode 100644 build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle delete mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml delete mode 100644 build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/build.gradle delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/build.gradle delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/settings.gradle delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile-aarch64 delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/README.adoc delete mode 100644 integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/logback.xml delete mode 100644 loader/spring-boot-loader-classic/build.gradle delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/JarLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/Launcher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/MainMethodRunner.java delete mode 100755 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/WarLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/Archive.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java delete mode 100755 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AbstractJarFile.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Bytes.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryVisitor.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/FileHeader.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Handler.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntry.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryCertification.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryFilter.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFile.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileWrapper.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/StringSequence.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/ZipInflaterInputStream.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarMode.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeErrorException.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/TestJarMode.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java delete mode 100644 loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/package-info.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/ClassPathIndexFileTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/JarLauncherTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/TestJarCreator.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/WarLauncherTests.java delete mode 100755 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java delete mode 100755 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/AsciiBytesTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileWrapperTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/StringSequenceTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jarmode/LauncherJarModeTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/util/SystemPropertyUtilsTests.java delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/application.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/bar.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/foo.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/loader.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/META-INF/spring.factories delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/bar.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/explodedsample/ExampleClass.txt delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/home/loader.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/jars/app.jar delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/jars/mismatch.jar delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/more-jars/app.jar delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/nested-jars/app.jar delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/nested-jars/nested-jar-app.jar delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/org/springframework/boot/loader/classpath-index-file.idx delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/placeholders/META-INF/MANIFEST.MF delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/placeholders/loader.properties delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/root/META-INF/MANIFEST.MF delete mode 100644 loader/spring-boot-loader-classic/src/test/resources/root/META-INF/spring/application.xml delete mode 100644 loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java index 8674aa18413..deeadcb9a00 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchive.java @@ -34,8 +34,6 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.jspecify.annotations.Nullable; -import org.springframework.boot.loader.tools.LoaderImplementation; - /** * A Spring Boot "fat" archive task. * @@ -137,15 +135,6 @@ public interface BootArchive extends Task { */ void resolvedArtifacts(Provider> resolvedArtifacts); - /** - * The loader implementation that should be used with the archive. - * @return the loader implementation - * @since 3.2.0 - */ - @Input - @Optional - Property getLoaderImplementation(); - /** * Returns whether the JAR tools should be included as a dependency in the layered * archive. diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java index 146ddec40fb..27dccf61b35 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootArchiveSupport.java @@ -46,8 +46,6 @@ import org.gradle.api.tasks.util.PatternSet; import org.gradle.util.GradleVersion; import org.jspecify.annotations.Nullable; -import org.springframework.boot.loader.tools.LoaderImplementation; - /** * Support class for implementations of {@link BootArchive}. * @@ -123,13 +121,11 @@ class BootArchiveSupport { return (version != null) ? version : "unknown"; } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, - LoaderImplementation loaderImplementation, boolean supportsSignatureFile) { - return createCopyAction(jar, resolvedDependencies, loaderImplementation, supportsSignatureFile, null, null); + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile) { + return createCopyAction(jar, resolvedDependencies, supportsSignatureFile, null, null); } - CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, - LoaderImplementation loaderImplementation, boolean supportsSignatureFile, + CopyAction createCopyAction(Jar jar, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, @Nullable LayerResolver layerResolver, @Nullable String jarmodeToolsLocation) { File output = jar.getArchiveFile().get().getAsFile(); Manifest manifest = jar.getManifest(); @@ -145,8 +141,7 @@ class BootArchiveSupport { String encoding = jar.getMetadataCharset(); CopyAction action = new BootZipCopyAction(output, manifest, preserveFileTimestamps, dirPermissions, filePermissions, includeDefaultLoader, jarmodeToolsLocation, requiresUnpack, exclusions, launchScript, - librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver, - loaderImplementation); + librarySpec, compressionResolver, encoding, resolvedDependencies, supportsSignatureFile, layerResolver); return jar.isReproducibleFileOrder() ? new ReproducibleOrderingCopyAction(action) : action; } diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java index 18cb1afe8b0..5e3ad8f2362 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootJar.java @@ -38,8 +38,6 @@ import org.gradle.api.tasks.bundling.Jar; import org.gradle.work.DisableCachingByDefault; import org.jspecify.annotations.Nullable; -import org.springframework.boot.loader.tools.LoaderImplementation; - /** * A custom {@link Jar} task that produces a Spring Boot executable jar. * @@ -145,13 +143,12 @@ public abstract class BootJar extends Jar implements BootArchive { @Override protected CopyAction createCopyAction() { - LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); LayerResolver layerResolver = null; if (!isLayeredDisabled()) { layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, true, layerResolver, + return this.support.createCopyAction(this, this.resolvedDependencies, true, layerResolver, jarmodeToolsLocation); } diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java index 4683969c73c..970f25335f8 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootWar.java @@ -38,8 +38,6 @@ import org.gradle.api.tasks.bundling.War; import org.gradle.work.DisableCachingByDefault; import org.jspecify.annotations.Nullable; -import org.springframework.boot.loader.tools.LoaderImplementation; - /** * A custom {@link War} task that produces a Spring Boot executable war. * @@ -119,14 +117,13 @@ public abstract class BootWar extends War implements BootArchive { @Override protected CopyAction createCopyAction() { - LoaderImplementation loaderImplementation = getLoaderImplementation().getOrElse(LoaderImplementation.DEFAULT); LayerResolver layerResolver = null; if (!isLayeredDisabled()) { layerResolver = new LayerResolver(this.resolvedDependencies, this.layered, this::isLibrary); } String jarmodeToolsLocation = isIncludeJarmodeTools() ? LIB_DIRECTORY : null; - return this.support.createCopyAction(this, this.resolvedDependencies, loaderImplementation, false, - layerResolver, jarmodeToolsLocation); + return this.support.createCopyAction(this, this.resolvedDependencies, false, layerResolver, + jarmodeToolsLocation); } private boolean isIncludeJarmodeTools() { diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java index 8974fe202bd..15110733042 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/BootZipCopyAction.java @@ -63,7 +63,6 @@ import org.springframework.boot.loader.tools.JarModeLibrary; import org.springframework.boot.loader.tools.Layer; import org.springframework.boot.loader.tools.LayersIndex; import org.springframework.boot.loader.tools.LibraryCoordinates; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.NativeImageArgFile; import org.springframework.boot.loader.tools.ReachabilityMetadataProperties; import org.springframework.util.Assert; @@ -120,15 +119,13 @@ class BootZipCopyAction implements CopyAction { private final @Nullable LayerResolver layerResolver; - private final LoaderImplementation loaderImplementation; - BootZipCopyAction(File output, Manifest manifest, boolean preserveFileTimestamps, @Nullable Integer dirMode, @Nullable Integer fileMode, boolean includeDefaultLoader, @Nullable String jarmodeToolsLocation, Spec requiresUnpack, Spec exclusions, @Nullable LaunchScriptConfiguration launchScript, Spec librarySpec, Function compressionResolver, @Nullable String encoding, ResolvedDependencies resolvedDependencies, boolean supportsSignatureFile, - @Nullable LayerResolver layerResolver, LoaderImplementation loaderImplementation) { + @Nullable LayerResolver layerResolver) { this.output = output; this.manifest = manifest; this.preserveFileTimestamps = preserveFileTimestamps; @@ -145,7 +142,6 @@ class BootZipCopyAction implements CopyAction { this.resolvedDependencies = resolvedDependencies; this.supportsSignatureFile = supportsSignatureFile; this.layerResolver = layerResolver; - this.loaderImplementation = loaderImplementation; } @Override @@ -329,8 +325,7 @@ class BootZipCopyAction implements CopyAction { // Always write loader entries after META-INF directory (see gh-16698) return; } - LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode(), - BootZipCopyAction.this.loaderImplementation); + LoaderZipEntries loaderEntries = new LoaderZipEntries(getTime(), getDirMode(), getFileMode()); this.writtenLoaderEntries = loaderEntries.writeTo(this.out); if (BootZipCopyAction.this.layerResolver != null) { for (String name : this.writtenLoaderEntries.getFiles()) { diff --git a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java index 786b9c85409..451237097fc 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java +++ b/build-plugin/spring-boot-gradle-plugin/src/main/java/org/springframework/boot/gradle/tasks/bundling/LoaderZipEntries.java @@ -29,7 +29,6 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.gradle.api.file.FileTreeElement; import org.jspecify.annotations.Nullable; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.Assert; import org.springframework.util.StreamUtils; @@ -42,27 +41,22 @@ import org.springframework.util.StreamUtils; */ class LoaderZipEntries { - private final LoaderImplementation loaderImplementation; - private final @Nullable Long entryTime; private final int dirMode; private final int fileMode; - LoaderZipEntries(@Nullable Long entryTime, int dirMode, int fileMode, - @Nullable LoaderImplementation loaderImplementation) { + LoaderZipEntries(@Nullable Long entryTime, int dirMode, int fileMode) { this.entryTime = entryTime; this.dirMode = dirMode; this.fileMode = fileMode; - this.loaderImplementation = (loaderImplementation != null) ? loaderImplementation - : LoaderImplementation.DEFAULT; } WrittenEntries writeTo(ZipArchiveOutputStream out) throws IOException { WrittenEntries written = new WrittenEntries(); try (ZipInputStream loaderJar = new ZipInputStream( - getResourceAsStream("/" + this.loaderImplementation.getJarResourceName()))) { + getResourceAsStream("/META-INF/loader/spring-boot-loader.jar"))) { java.util.zip.ZipEntry entry = loaderJar.getNextEntry(); while (entry != null) { if (entry.isDirectory() && !entry.getName().equals("META-INF/")) { diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java index 3a379b6364e..43c30e37f2a 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveIntegrationTests.java @@ -108,16 +108,6 @@ abstract class AbstractBootArchiveIntegrationTests { assertThat(firstHash).isEqualTo(secondHash); } - @TestTemplate - void classicLoader() throws IOException { - assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) - .isEqualTo(TaskOutcome.SUCCESS); - File jar = new File(this.gradleBuild.getProjectDir(), "build/libs").listFiles()[0]; - try (JarFile jarFile = new JarFile(jar)) { - assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); - } - } - @TestTemplate void upToDateWhenBuiltTwice() { assertThat(this.gradleBuild.build(this.taskName).task(":" + this.taskName).getOutcome()) diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java index fe891b76ab8..a369f0b4667 100644 --- a/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java +++ b/build-plugin/spring-boot-gradle-plugin/src/test/java/org/springframework/boot/gradle/tasks/bundling/AbstractBootArchiveTests.java @@ -65,7 +65,6 @@ import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.gradle.junit.GradleProjectBuilder; import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.JarModeLibrary; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.FileCopyUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -283,17 +282,6 @@ abstract class AbstractBootArchiveTests { } } - @Test - void loaderIsWrittenToTheRootOfTheJarWhenUsingClassicLoader() throws IOException { - this.task.getMainClass().set("com.example.Main"); - this.task.getLoaderImplementation().set(LoaderImplementation.CLASSIC); - executeTask(); - try (JarFile jarFile = new JarFile(this.task.getArchiveFile().get().getAsFile())) { - assertThat(jarFile.getEntry("org/springframework/boot/loader/LaunchedURLClassLoader.class")).isNotNull(); - assertThat(jarFile.getEntry("org/springframework/boot/loader/")).isNotNull(); - } - } - @Test void unpackCommentIsAddedToEntryIdentifiedByAPattern() throws IOException { this.task.getMainClass().set("com.example.Main"); diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle deleted file mode 100644 index b8224fa3c30..00000000000 --- a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootJarIntegrationTests-classicLoader.gradle +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id 'java' - id 'org.springframework.boot' version '{version}' -} - -bootJar { - mainClass = 'com.example.Application' - loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC -} diff --git a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle b/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle deleted file mode 100644 index 77668f5d9b5..00000000000 --- a/build-plugin/spring-boot-gradle-plugin/src/test/resources/org/springframework/boot/gradle/tasks/bundling/BootWarIntegrationTests-classicLoader.gradle +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id 'war' - id 'org.springframework.boot' version '{version}' -} - -bootWar { - mainClass = 'com.example.Application' - loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC -} diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java index 2f2cc0b9cca..222d45aa3ad 100644 --- a/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java +++ b/build-plugin/spring-boot-maven-plugin/src/intTest/java/org/springframework/boot/maven/JarIntegrationTests.java @@ -76,26 +76,6 @@ class JarIntegrationTests extends AbstractArchiveIntegrationTests { }); } - @TestTemplate - void whenJarWithClassicLoaderIsRepackagedInPlaceOnlyRepackagedJarIsInstalled(MavenBuild mavenBuild) { - mavenBuild.project("jar-with-classic-loader").goals("install").execute((project) -> { - File original = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar.original"); - assertThat(original).isFile(); - File repackaged = new File(project, "target/jar-with-classic-loader-0.0.1.BUILD-SNAPSHOT.jar"); - assertThat(launchScript(repackaged)).isEmpty(); - assertThat(jar(repackaged)).manifest((manifest) -> { - manifest.hasMainClass("org.springframework.boot.loader.launch.JarLauncher"); - manifest.hasStartClass("some.random.Main"); - manifest.hasAttribute("Not-Used", "Foo"); - }).hasEntryWithName("org/springframework/boot/loader/launch/JarLauncher.class"); - assertThat(buildLog(project)) - .contains("Replacing main artifact " + repackaged + " with repackaged archive,") - .contains("The original artifact has been renamed to " + original) - .contains("Installing " + repackaged + " to") - .doesNotContain("Installing " + original + " to"); - }); - } - @TestTemplate void whenAttachIsDisabledOnlyTheOriginalJarIsInstalled(MavenBuild mavenBuild) { mavenBuild.project("jar-attach-disabled").goals("install").execute((project) -> { diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml deleted file mode 100644 index ce29e60f402..00000000000 --- a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - 4.0.0 - org.springframework.boot.maven.it - jar-with-classic-loader - 0.0.1.BUILD-SNAPSHOT - - UTF-8 - @java.version@ - @java.version@ - - - - - @project.groupId@ - @project.artifactId@ - @project.version@ - - - - repackage - - - CLASSIC - - - - - - org.apache.maven.plugins - maven-jar-plugin - @maven-jar-plugin.version@ - - - - some.random.Main - - - Foo - - - - - - - - - org.springframework - spring-context - @spring-framework.version@ - - - jakarta.servlet - jakarta.servlet-api - @jakarta-servlet.version@ - provided - - - diff --git a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java b/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java deleted file mode 100644 index 547d0cf0171..00000000000 --- a/build-plugin/spring-boot-maven-plugin/src/intTest/projects/jar-with-classic-loader/src/main/java/org/test/SampleApplication.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.test; - -public class SampleApplication { - - public static void main(String[] args) { - } - -} diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java index 6de78382df3..37cc11f47d6 100644 --- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java +++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/AbstractPackagerMojo.java @@ -47,7 +47,6 @@ import org.springframework.boot.loader.tools.Layouts.Jar; import org.springframework.boot.loader.tools.Layouts.None; import org.springframework.boot.loader.tools.Layouts.War; import org.springframework.boot.loader.tools.Libraries; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.Packager; import org.springframework.boot.loader.tools.layer.CustomLayers; @@ -142,15 +141,6 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo return null; } - /** - * Return the loader implementation that should be used. - * @return the loader implementation or {@code null} - * @since 3.2.0 - */ - protected @Nullable LoaderImplementation getLoaderImplementation() { - return null; - } - /** * Return the layout factory that will be used to determine the {@link LayoutType} if * no explicit layout is set. @@ -168,7 +158,6 @@ public abstract class AbstractPackagerMojo extends AbstractDependencyFilterMojo */ protected

P getConfiguredPackager(Supplier

supplier) { P packager = supplier.get(); - packager.setLoaderImplementation(getLoaderImplementation()); packager.setLayoutFactory(getLayoutFactory()); packager.addMainClassTimeoutWarningListener(new LoggingMainClassTimeoutWarningListener(this::getLog)); packager.setMainClass(this.mainClass); diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java index 2ac7f7d5f6c..24b07f85b93 100644 --- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java +++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/BuildImageMojo.java @@ -50,7 +50,6 @@ import org.springframework.boot.loader.tools.EntryWriter; import org.springframework.boot.loader.tools.ImagePackager; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -208,13 +207,6 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo { @Parameter private @Nullable LayoutType layout; - /** - * The loader implementation that should be used. - * @since 3.2.0 - */ - @Parameter - private @Nullable LoaderImplementation loaderImplementation; - /** * The layout factory that will be used to create the executable archive if no * explicit layout is set. Alternative layouts implementations can be provided by 3rd @@ -238,11 +230,6 @@ public abstract class BuildImageMojo extends AbstractPackagerMojo { return this.layout; } - @Override - protected @Nullable LoaderImplementation getLoaderImplementation() { - return this.loaderImplementation; - } - /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. diff --git a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java index b23a2226430..2ccad7b25f1 100644 --- a/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java +++ b/build-plugin/spring-boot-maven-plugin/src/main/java/org/springframework/boot/maven/RepackageMojo.java @@ -40,7 +40,6 @@ import org.springframework.boot.loader.tools.DefaultLaunchScript; import org.springframework.boot.loader.tools.LaunchScript; import org.springframework.boot.loader.tools.LayoutFactory; import org.springframework.boot.loader.tools.Libraries; -import org.springframework.boot.loader.tools.LoaderImplementation; import org.springframework.boot.loader.tools.Repackager; import org.springframework.lang.Contract; import org.springframework.util.StringUtils; @@ -170,13 +169,6 @@ public class RepackageMojo extends AbstractPackagerMojo { @Parameter(property = "spring-boot.repackage.layout") private @Nullable LayoutType layout; - /** - * The loader implementation that should be used. - * @since 3.2.0 - */ - @Parameter - private @Nullable LoaderImplementation loaderImplementation; - /** * The layout factory that will be used to create the executable archive if no * explicit layout is set. Alternative layouts implementations can be provided by 3rd @@ -201,11 +193,6 @@ public class RepackageMojo extends AbstractPackagerMojo { return this.layout; } - @Override - protected @Nullable LoaderImplementation getLoaderImplementation() { - return this.loaderImplementation; - } - /** * Return the layout factory that will be used to determine the * {@link AbstractPackagerMojo.LayoutType} if no explicit layout is set. diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index bc93c50f842..8941ea9abe7 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -246,7 +246,6 @@ project.rootProject.gradle.projectsEvaluated { .findAll { !it.path.contains(":configuration-metadata:") } .findAll { !it.path.contains(":core:spring-boot-properties-migrator") } .findAll { !it.path.contains(":loader:spring-boot-jarmode-tools") } - .findAll { !it.path.contains(":loader:spring-boot-loader-classic") } .findAll { !it.name.startsWith('spring-boot-starter') } aggregatedJavadoc.configure { dependsOn publishedProjects.javadoc diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc index 4a8efab2fbc..ee6a11ed917 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/ROOT/pages/redirect.adoc @@ -765,7 +765,6 @@ * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layers[maven-plugin#build-image.build-image-goal.parameter-details.layers] * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layout-factory[maven-plugin#build-image.build-image-goal.parameter-details.layout-factory] * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.layout[maven-plugin#build-image.build-image-goal.parameter-details.layout] -* xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.loader-implementation[maven-plugin#build-image.build-image-goal.parameter-details.loader-implementation] * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.main-class[maven-plugin#build-image.build-image-goal.parameter-details.main-class] * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.skip[maven-plugin#build-image.build-image-goal.parameter-details.skip] * xref:maven-plugin:build-image.adoc#build-image.build-image-goal.parameter-details.source-directory[maven-plugin#build-image.build-image-goal.parameter-details.source-directory] @@ -786,7 +785,6 @@ * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layers[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layers] * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layout-factory[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layout-factory] * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.layout[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.layout] -* xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.loader-implementation[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.loader-implementation] * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.main-class[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.main-class] * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.skip[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.skip] * xref:maven-plugin:build-image.adoc#build-image.build-image-no-fork-goal.parameter-details.source-directory[maven-plugin#build-image.build-image-no-fork-goal.parameter-details.source-directory] @@ -896,7 +894,6 @@ * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layers[maven-plugin#packaging.repackage-goal.parameter-details.layers] * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layout-factory[maven-plugin#packaging.repackage-goal.parameter-details.layout-factory] * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.layout[maven-plugin#packaging.repackage-goal.parameter-details.layout] -* xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.loader-implementation[maven-plugin#packaging.repackage-goal.parameter-details.loader-implementation] * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.main-class[maven-plugin#packaging.repackage-goal.parameter-details.main-class] * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.output-directory[maven-plugin#packaging.repackage-goal.parameter-details.output-directory] * xref:maven-plugin:packaging.adoc#packaging.repackage-goal.parameter-details.output-timestamp[maven-plugin#packaging.repackage-goal.parameter-details.output-timestamp] diff --git a/eclipse/spring-boot-project.setup b/eclipse/spring-boot-project.setup index 8a52876eace..aa6166a4f33 100644 --- a/eclipse/spring-boot-project.setup +++ b/eclipse/spring-boot-project.setup @@ -128,7 +128,7 @@ name="spring-boot-tools"> + pattern="spring-boot-(tools|antlib|configuration-.*|loader|.*-tools|.*-layertools|.*-plugin|autoconfigure-processor|buildpack.*)"/> diff --git a/integration-test/spring-boot-loader-classic-integration-tests/build.gradle b/integration-test/spring-boot-loader-classic-integration-tests/build.gradle deleted file mode 100644 index 8ba3054e602..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id "java" - id "org.springframework.boot.docker-test" -} - -description = "Spring Boot Classic Loader Integration Tests" - -configurations { - app -} - -dependencies { - app project(path: ":platform:spring-boot-dependencies", configuration: "mavenRepository") - app project(path: ":build-plugin:spring-boot-gradle-plugin", configuration: "mavenRepository") - app project(path: ":starter:spring-boot-starter-web", configuration: "mavenRepository") - - dockerTestImplementation(project(":test-support:spring-boot-docker-test-support")) - dockerTestImplementation(project(":starter:spring-boot-starter-test")) - dockerTestImplementation("org.testcontainers:junit-jupiter") - dockerTestImplementation("org.testcontainers:testcontainers") -} - -tasks.register("syncMavenRepository", Sync) { - from configurations.app - into layout.buildDirectory.dir("docker-test-maven-repository") -} - -tasks.register("syncAppSource", org.springframework.boot.build.SyncAppSource) { - sourceDirectory = file("spring-boot-loader-classic-tests-app") - destinationDirectory = file(layout.buildDirectory.dir("spring-boot-loader-classic-tests-app")) -} - -tasks.register("buildApp", GradleBuild) { - dependsOn syncAppSource, syncMavenRepository - dir = layout.buildDirectory.dir("spring-boot-loader-classic-tests-app") - startParameter.buildCacheEnabled = false - tasks = ["build"] -} - -tasks.named("dockerTest").configure { - dependsOn buildApp -} diff --git a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/build.gradle b/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/build.gradle deleted file mode 100644 index bc8af9d3ad1..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id "java" - id "org.springframework.boot" -} - -java { - sourceCompatibility = '17' - targetCompatibility = '17' -} - -repositories { - maven { url "file:${rootDir}/../docker-test-maven-repository"} - mavenCentral() - spring.mavenRepositories() -} - -dependencies { - implementation(platform(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.webjars:jquery:3.5.0") -} - -bootJar { - loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC -} \ No newline at end of file diff --git a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/settings.gradle b/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/settings.gradle deleted file mode 100644 index 8f2eaa44c48..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/settings.gradle +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -pluginManagement { - evaluate(new File("${gradle.parent.rootProject.rootDir}/buildSrc/SpringRepositorySupport.groovy")).apply(this) - repositories { - maven { url "file:${rootDir}/../docker-test-maven-repository"} - mavenCentral() - spring.mavenRepositories() - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == "org.springframework.boot") { - useModule "org.springframework.boot:spring-boot-gradle-plugin:${requested.version}" - } - } - } -} \ No newline at end of file diff --git a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java b/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java deleted file mode 100644 index 49641e20e89..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/spring-boot-loader-classic-tests-app/src/main/java/org/springframework/boot/loaderapp/LoaderTestApplication.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loaderapp; - -import java.io.File; -import java.net.JarURLConnection; -import java.net.URL; -import java.util.Arrays; - -import jakarta.servlet.ServletContext; - -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.util.FileCopyUtils; - -@SpringBootApplication -public class LoaderTestApplication { - - @Bean - public CommandLineRunner commandLineRunner(ServletContext servletContext) { - return (args) -> { - File temp = new File(System.getProperty("java.io.tmpdir")); - URL resourceUrl = servletContext.getResource("webjars/jquery/3.5.0/jquery.js"); - JarURLConnection connection = (JarURLConnection) resourceUrl.openConnection(); - String jarName = connection.getJarFile().getName(); - System.out.println(">>>>> jar file " + jarName); - if(jarName.contains(temp.getAbsolutePath())) { - System.out.println(">>>>> jar written to temp"); - } - byte[] resourceContent = FileCopyUtils.copyToByteArray(resourceUrl.openStream()); - URL directUrl = new URL(resourceUrl.toExternalForm()); - byte[] directContent = FileCopyUtils.copyToByteArray(directUrl.openStream()); - String message = (!Arrays.equals(resourceContent, directContent)) ? "NO MATCH" - : directContent.length + " BYTES"; - System.out.println(">>>>> " + message + " from " + resourceUrl); - }; - } - - public static void main(String[] args) { - SpringApplication.run(LoaderTestApplication.class, args).close(); - } - -} diff --git a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java b/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java deleted file mode 100644 index 588a282108e..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/java/org/springframework/boot/loader/LoaderIntegrationTests.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.output.ToStringConsumer; -import org.testcontainers.containers.startupcheck.OneShotStartupCheckStrategy; -import org.testcontainers.images.builder.ImageFromDockerfile; -import org.testcontainers.utility.DockerImageName; -import org.testcontainers.utility.MountableFile; - -import org.springframework.boot.system.JavaVersion; -import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable; -import org.springframework.util.Assert; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests loader that supports uber jars. - * - * @author Phillip Webb - * @author Moritz Halbritter - */ -@DisabledIfDockerUnavailable -class LoaderIntegrationTests { - - private final ToStringConsumer output = new ToStringConsumer(); - - @ParameterizedTest - @MethodSource("javaRuntimes") - void readUrlsWithoutWarning(JavaRuntime javaRuntime) { - try (GenericContainer container = createContainer(javaRuntime)) { - container.start(); - System.out.println(this.output.toUtf8String()); - assertThat(this.output.toUtf8String()).contains(">>>>> 287649 BYTES from") - .doesNotContain("WARNING:") - .doesNotContain("illegal") - .doesNotContain("jar written to temp"); - } - } - - private GenericContainer createContainer(JavaRuntime javaRuntime) { - return javaRuntime.getContainer() - .withLogConsumer(this.output) - .withCopyFileToContainer(MountableFile.forHostPath(findApplication().toPath()), "/app.jar") - .withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofMinutes(5))) - .withCommand(command()); - } - - private String[] command() { - List command = new ArrayList<>(); - command.add("java"); - command.add("-jar"); - command.add("app.jar"); - return command.toArray(new String[0]); - } - - private File findApplication() { - String name = String.format("build/%1$s/build/libs/%1$s.jar", "spring-boot-loader-classic-tests-app"); - File jar = new File(name); - Assert.state(jar.isFile(), () -> "Could not find " + name + ". Have you built it?"); - return jar; - } - - static Stream javaRuntimes() { - List javaRuntimes = new ArrayList<>(); - javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.SEVENTEEN)); - javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.TWENTY_ONE)); - javaRuntimes.add(JavaRuntime.oracleJdk17()); - javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.TWENTY_TWO)); - javaRuntimes.add(JavaRuntime.openJdk(JavaVersion.TWENTY_THREE)); - javaRuntimes.add(JavaRuntime.openJdkEarlyAccess(JavaVersion.TWENTY_FOUR)); - return javaRuntimes.stream().filter(JavaRuntime::isCompatible); - } - - static final class JavaRuntime { - - private final String name; - - private final JavaVersion version; - - private final Supplier> container; - - private JavaRuntime(String name, JavaVersion version, Supplier> container) { - this.name = name; - this.version = version; - this.container = container; - } - - private boolean isCompatible() { - return this.version.isEqualOrNewerThan(JavaVersion.getJavaVersion()); - } - - GenericContainer getContainer() { - return this.container.get(); - } - - @Override - public String toString() { - return this.name; - } - - static JavaRuntime openJdkEarlyAccess(JavaVersion version) { - String imageVersion = version.toString(); - DockerImageName image = DockerImageName.parse("openjdk:%s-ea-jdk".formatted(imageVersion)); - return new JavaRuntime("OpenJDK Early Access " + imageVersion, version, - () -> new GenericContainer<>(image)); - } - - static JavaRuntime openJdk(JavaVersion version) { - String imageVersion = version.toString(); - DockerImageName image = DockerImageName.parse("bellsoft/liberica-openjdk-debian:" + imageVersion); - return new JavaRuntime("OpenJDK " + imageVersion, version, () -> new GenericContainer<>(image)); - } - - static JavaRuntime oracleJdk17() { - String arch = System.getProperty("os.arch"); - String dockerFile = ("aarch64".equals(arch)) ? "Dockerfile-aarch64" : "Dockerfile"; - ImageFromDockerfile image = new ImageFromDockerfile("spring-boot-loader/oracle-jdk-17") - .withFileFromFile("Dockerfile", new File("src/dockerTest/resources/conf/oracle-jdk-17/" + dockerFile)); - return new JavaRuntime("Oracle JDK 17", JavaVersion.SEVENTEEN, () -> new GenericContainer<>(image)); - } - - } - -} diff --git a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile b/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile deleted file mode 100644 index 33977a8656c..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:noble-20250404 -RUN apt-get update && \ - apt-get install -y software-properties-common curl && \ - mkdir -p /opt/oraclejdk && \ - cd /opt/oraclejdk && \ - curl -L https://download.oracle.com/java/17/archive/jdk-17_linux-x64_bin.tar.gz | tar zx --strip-components=1 -ENV JAVA_HOME /opt/oraclejdk -ENV PATH $JAVA_HOME/bin:$PATH diff --git a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile-aarch64 b/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile-aarch64 deleted file mode 100644 index 7e4a5cceeb3..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/Dockerfile-aarch64 +++ /dev/null @@ -1,8 +0,0 @@ -FROM ubuntu:noble-20250404 -RUN apt-get update && \ - apt-get install -y software-properties-common curl && \ - mkdir -p /opt/oraclejdk && \ - cd /opt/oraclejdk && \ - curl -L https://download.oracle.com/java/17/archive/jdk-17.0.8_linux-aarch64_bin.tar.gz | tar zx --strip-components=1 -ENV JAVA_HOME /opt/oraclejdk -ENV PATH $JAVA_HOME/bin:$PATH diff --git a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/README.adoc b/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/README.adoc deleted file mode 100644 index 28704af225f..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/conf/oracle-jdk-17/README.adoc +++ /dev/null @@ -1,5 +0,0 @@ -This folder contains a Dockerfile that will create an Oracle JDK instance for use in integration tests. -The resulting Docker image should not be published. - -Oracle JDK is subject to the https://www.oracle.com/downloads/licenses/no-fee-license.html["Oracle No-Fee Terms and Conditions" License (NFTC)] license. -We are specifically using the unmodified JDK for the purposes of developing and testing. diff --git a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/logback.xml b/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/logback.xml deleted file mode 100644 index b8a41480d7d..00000000000 --- a/integration-test/spring-boot-loader-classic-integration-tests/src/dockerTest/resources/logback.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/loader/spring-boot-loader-classic/build.gradle b/loader/spring-boot-loader-classic/build.gradle deleted file mode 100644 index 80d93406e03..00000000000 --- a/loader/spring-boot-loader-classic/build.gradle +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -plugins { - id "java-library" - id "org.springframework.boot.deployed" -} - -description = "Spring Boot Classic Loader" - -dependencies { - compileOnly("org.springframework:spring-core") - - testImplementation(project(":test-support:spring-boot-test-support")) - - testRuntimeOnly("ch.qos.logback:logback-classic") - testRuntimeOnly("org.bouncycastle:bcprov-jdk18on:1.78.1") - testRuntimeOnly("org.springframework:spring-webmvc") -} - -tasks.configureEach { - if ("checkArchitectureMain".equals(it.name)) { - prohibitObjectsRequireNonNull = false - } -} - -architectureCheck { - nullMarked = false -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java deleted file mode 100644 index 6f53c84d052..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ClassPathIndexFile.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A class path index file that provides ordering information for JARs. - * - * @author Madhura Bhave - * @author Phillip Webb - */ -final class ClassPathIndexFile { - - private final File root; - - private final List lines; - - private ClassPathIndexFile(File root, List lines) { - this.root = root; - this.lines = lines.stream().map(this::extractName).toList(); - } - - private String extractName(String line) { - if (line.startsWith("- \"") && line.endsWith("\"")) { - return line.substring(3, line.length() - 1); - } - throw new IllegalStateException("Malformed classpath index line [" + line + "]"); - } - - int size() { - return this.lines.size(); - } - - boolean containsEntry(String name) { - if (name == null || name.isEmpty()) { - return false; - } - return this.lines.contains(name); - } - - List getUrls() { - return this.lines.stream().map(this::asUrl).toList(); - } - - private URL asUrl(String line) { - try { - return new File(this.root, line).toURI().toURL(); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - static ClassPathIndexFile loadIfPossible(URL root, String location) throws IOException { - return loadIfPossible(asFile(root), location); - } - - private static ClassPathIndexFile loadIfPossible(File root, String location) throws IOException { - return loadIfPossible(root, new File(root, location)); - } - - private static ClassPathIndexFile loadIfPossible(File root, File indexFile) throws IOException { - if (indexFile.exists() && indexFile.isFile()) { - try (InputStream inputStream = new FileInputStream(indexFile)) { - return new ClassPathIndexFile(root, loadLines(inputStream)); - } - } - return null; - } - - private static List loadLines(InputStream inputStream) throws IOException { - List lines = new ArrayList<>(); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); - String line = reader.readLine(); - while (line != null) { - if (!line.trim().isEmpty()) { - lines.add(line); - } - line = reader.readLine(); - } - return Collections.unmodifiableList(lines); - } - - private static File asFile(URL url) { - if (!"file".equals(url.getProtocol())) { - throw new IllegalArgumentException("URL does not reference a file"); - } - try { - return new File(url.toURI()); - } - catch (URISyntaxException ex) { - return new File(url.getPath()); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java deleted file mode 100644 index 90fe6b8736e..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; - -/** - * Base class for executable archive {@link Launcher}s. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Scott Frederick - * @since 1.0.0 - */ -public abstract class ExecutableArchiveLauncher extends Launcher { - - private static final String START_CLASS_ATTRIBUTE = "Start-Class"; - - protected static final String BOOT_CLASSPATH_INDEX_ATTRIBUTE = "Spring-Boot-Classpath-Index"; - - protected static final String DEFAULT_CLASSPATH_INDEX_FILE_NAME = "classpath.idx"; - - private final Archive archive; - - private final ClassPathIndexFile classPathIndex; - - public ExecutableArchiveLauncher() { - try { - this.archive = createArchive(); - this.classPathIndex = getClassPathIndex(this.archive); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - protected ExecutableArchiveLauncher(Archive archive) { - try { - this.archive = archive; - this.classPathIndex = getClassPathIndex(this.archive); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - protected ClassPathIndexFile getClassPathIndex(Archive archive) throws IOException { - // Only needed for exploded archives, regular ones already have a defined order - if (archive instanceof ExplodedArchive) { - String location = getClassPathIndexFileLocation(archive); - return ClassPathIndexFile.loadIfPossible(archive.getUrl(), location); - } - return null; - } - - private String getClassPathIndexFileLocation(Archive archive) throws IOException { - Manifest manifest = archive.getManifest(); - Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; - String location = (attributes != null) ? attributes.getValue(BOOT_CLASSPATH_INDEX_ATTRIBUTE) : null; - return (location != null) ? location : getArchiveEntryPathPrefix() + DEFAULT_CLASSPATH_INDEX_FILE_NAME; - } - - @Override - protected String getMainClass() throws Exception { - Manifest manifest = this.archive.getManifest(); - String mainClass = null; - if (manifest != null) { - mainClass = manifest.getMainAttributes().getValue(START_CLASS_ATTRIBUTE); - } - if (mainClass == null) { - throw new IllegalStateException("No 'Start-Class' manifest entry specified in " + this); - } - return mainClass; - } - - @Override - protected ClassLoader createClassLoader(Iterator archives) throws Exception { - List urls = new ArrayList<>(guessClassPathSize()); - while (archives.hasNext()) { - urls.add(archives.next().getUrl()); - } - if (this.classPathIndex != null) { - urls.addAll(this.classPathIndex.getUrls()); - } - return createClassLoader(urls.toArray(new URL[0])); - } - - private int guessClassPathSize() { - if (this.classPathIndex != null) { - return this.classPathIndex.size() + 10; - } - return 50; - } - - @Override - protected Iterator getClassPathArchivesIterator() throws Exception { - Archive.EntryFilter searchFilter = this::isSearchCandidate; - Iterator archives = this.archive.getNestedArchives(searchFilter, - (entry) -> isNestedArchive(entry) && !isEntryIndexed(entry)); - if (isPostProcessingClassPathArchives()) { - archives = applyClassPathArchivePostProcessing(archives); - } - return archives; - } - - private boolean isEntryIndexed(Archive.Entry entry) { - if (this.classPathIndex != null) { - return this.classPathIndex.containsEntry(entry.getName()); - } - return false; - } - - private Iterator applyClassPathArchivePostProcessing(Iterator archives) throws Exception { - List list = new ArrayList<>(); - while (archives.hasNext()) { - list.add(archives.next()); - } - postProcessClassPathArchives(list); - return list.iterator(); - } - - /** - * Determine if the specified entry is a candidate for further searching. - * @param entry the entry to check - * @return {@code true} if the entry is a candidate for further searching - * @since 2.3.0 - */ - protected boolean isSearchCandidate(Archive.Entry entry) { - if (getArchiveEntryPathPrefix() == null) { - return true; - } - return entry.getName().startsWith(getArchiveEntryPathPrefix()); - } - - /** - * Determine if the specified entry is a nested item that should be added to the - * classpath. - * @param entry the entry to check - * @return {@code true} if the entry is a nested item (jar or directory) - */ - protected abstract boolean isNestedArchive(Archive.Entry entry); - - /** - * Return if post-processing needs to be applied to the archives. For back - * compatibility this method returns {@code true}, but subclasses that don't override - * {@link #postProcessClassPathArchives(List)} should provide an implementation that - * returns {@code false}. - * @return if the {@link #postProcessClassPathArchives(List)} method is implemented - * @since 2.3.0 - */ - protected boolean isPostProcessingClassPathArchives() { - return true; - } - - /** - * Called to post-process archive entries before they are used. Implementations can - * add and remove entries. - * @param archives the archives - * @throws Exception if the post-processing fails - * @see #isPostProcessingClassPathArchives() - */ - protected void postProcessClassPathArchives(List archives) throws Exception { - } - - /** - * Return the path prefix for entries in the archive. - * @return the path prefix - */ - protected String getArchiveEntryPathPrefix() { - return null; - } - - @Override - protected boolean isExploded() { - return this.archive.isExploded(); - } - - @Override - protected final Archive getArchive() { - return this.archive; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/JarLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/JarLauncher.java deleted file mode 100644 index 418812a4f7d..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/JarLauncher.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.Archive.EntryFilter; - -/** - * {@link Launcher} for JAR based archives. This launcher assumes that dependency jars are - * included inside a {@code /BOOT-INF/lib} directory and that application classes are - * included inside a {@code /BOOT-INF/classes} directory. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Scott Frederick - * @since 1.0.0 - */ -public class JarLauncher extends ExecutableArchiveLauncher { - - static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> { - if (entry.isDirectory()) { - return entry.getName().equals("BOOT-INF/classes/"); - } - return entry.getName().startsWith("BOOT-INF/lib/"); - }; - - public JarLauncher() { - } - - protected JarLauncher(Archive archive) { - super(archive); - } - - @Override - protected boolean isPostProcessingClassPathArchives() { - return false; - } - - @Override - protected boolean isNestedArchive(Archive.Entry entry) { - return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry); - } - - @Override - protected String getArchiveEntryPathPrefix() { - return "BOOT-INF/"; - } - - public static void main(String[] args) throws Exception { - new JarLauncher().launch(args); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java deleted file mode 100644 index 314861b5112..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.function.Supplier; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.jar.Handler; - -/** - * {@link ClassLoader} used by the {@link Launcher}. - * - * @author Phillip Webb - * @author Dave Syer - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class LaunchedURLClassLoader extends URLClassLoader { - - private static final int BUFFER_SIZE = 4096; - - static { - ClassLoader.registerAsParallelCapable(); - } - - private final boolean exploded; - - private final Archive rootArchive; - - private final Object packageLock = new Object(); - - private volatile DefinePackageCallType definePackageCallType; - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - */ - public LaunchedURLClassLoader(URL[] urls, ClassLoader parent) { - this(false, urls, parent); - } - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param exploded if the underlying archive is exploded - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - */ - public LaunchedURLClassLoader(boolean exploded, URL[] urls, ClassLoader parent) { - this(exploded, null, urls, parent); - } - - /** - * Create a new {@link LaunchedURLClassLoader} instance. - * @param exploded if the underlying archive is exploded - * @param rootArchive the root archive or {@code null} - * @param urls the URLs from which to load classes and resources - * @param parent the parent class loader for delegation - * @since 2.3.1 - */ - public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) { - super(urls, parent); - this.exploded = exploded; - this.rootArchive = rootArchive; - } - - @Override - public URL findResource(String name) { - if (this.exploded) { - return super.findResource(name); - } - Handler.setUseFastConnectionExceptions(true); - try { - return super.findResource(name); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - @Override - public Enumeration findResources(String name) throws IOException { - if (this.exploded) { - return super.findResources(name); - } - Handler.setUseFastConnectionExceptions(true); - try { - return new UseFastConnectionExceptionsEnumeration(super.findResources(name)); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (name.startsWith("org.springframework.boot.loader.jarmode.")) { - try { - Class result = loadClassInLaunchedClassLoader(name); - if (resolve) { - resolveClass(result); - } - return result; - } - catch (ClassNotFoundException ex) { - // Ignore - } - } - if (this.exploded) { - return super.loadClass(name, resolve); - } - Handler.setUseFastConnectionExceptions(true); - try { - try { - definePackageIfNecessary(name); - } - catch (IllegalArgumentException ex) { - // Tolerate race condition due to being parallel capable - if (getDefinedPackage(name) == null) { - // This should never happen as the IllegalArgumentException indicates - // that the package has already been defined and, therefore, - // getDefinedPackage(name) should not return null. - throw new AssertionError("Package " + name + " has already been defined but it could not be found"); - } - } - return super.loadClass(name, resolve); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - private Class loadClassInLaunchedClassLoader(String name) throws ClassNotFoundException { - String internalName = name.replace('.', '/') + ".class"; - InputStream inputStream = getParent().getResourceAsStream(internalName); - if (inputStream == null) { - throw new ClassNotFoundException(name); - } - try { - try { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead = -1; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - inputStream.close(); - byte[] bytes = outputStream.toByteArray(); - Class definedClass = defineClass(name, bytes, 0, bytes.length); - definePackageIfNecessary(name); - return definedClass; - } - finally { - inputStream.close(); - } - } - catch (IOException ex) { - throw new ClassNotFoundException("Cannot load resource for class [" + name + "]", ex); - } - } - - /** - * Define a package before a {@code findClass} call is made. This is necessary to - * ensure that the appropriate manifest for nested JARs is associated with the - * package. - * @param className the class name being found - */ - private void definePackageIfNecessary(String className) { - int lastDot = className.lastIndexOf('.'); - if (lastDot >= 0) { - String packageName = className.substring(0, lastDot); - if (getDefinedPackage(packageName) == null) { - try { - definePackage(className, packageName); - } - catch (IllegalArgumentException ex) { - // Tolerate race condition due to being parallel capable - if (getDefinedPackage(packageName) == null) { - // This should never happen as the IllegalArgumentException - // indicates that the package has already been defined and, - // therefore, getDefinedPackage(name) should not have returned - // null. - throw new AssertionError( - "Package " + packageName + " has already been defined but it could not be found"); - } - } - } - } - } - - private void definePackage(String className, String packageName) { - String packageEntryName = packageName.replace('.', '/') + "/"; - String classEntryName = className.replace('.', '/') + ".class"; - for (URL url : getURLs()) { - try { - URLConnection connection = url.openConnection(); - if (connection instanceof JarURLConnection jarURLConnection) { - JarFile jarFile = jarURLConnection.getJarFile(); - if (jarFile.getEntry(classEntryName) != null && jarFile.getEntry(packageEntryName) != null - && jarFile.getManifest() != null) { - definePackage(packageName, jarFile.getManifest(), url); - return; - } - } - } - catch (IOException ex) { - // Ignore - } - } - } - - @Override - protected Package definePackage(String name, Manifest man, URL url) throws IllegalArgumentException { - if (!this.exploded) { - return super.definePackage(name, man, url); - } - synchronized (this.packageLock) { - return doDefinePackage(DefinePackageCallType.MANIFEST, () -> super.definePackage(name, man, url)); - } - } - - @Override - protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, - String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { - if (!this.exploded) { - return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, - sealBase); - } - synchronized (this.packageLock) { - if (this.definePackageCallType == null) { - // We're not part of a call chain which means that the URLClassLoader - // is trying to define a package for our exploded JAR. We use the - // manifest version to ensure package attributes are set - Manifest manifest = getManifest(this.rootArchive); - if (manifest != null) { - return definePackage(name, manifest, sealBase); - } - } - return doDefinePackage(DefinePackageCallType.ATTRIBUTES, () -> super.definePackage(name, specTitle, - specVersion, specVendor, implTitle, implVersion, implVendor, sealBase)); - } - } - - private Manifest getManifest(Archive archive) { - try { - return (archive != null) ? archive.getManifest() : null; - } - catch (IOException ex) { - return null; - } - } - - private T doDefinePackage(DefinePackageCallType type, Supplier call) { - DefinePackageCallType existingType = this.definePackageCallType; - try { - this.definePackageCallType = type; - return call.get(); - } - finally { - this.definePackageCallType = existingType; - } - } - - /** - * Clear URL caches. - */ - public void clearCache() { - if (this.exploded) { - return; - } - for (URL url : getURLs()) { - try { - URLConnection connection = url.openConnection(); - if (connection instanceof JarURLConnection) { - clearCache(connection); - } - } - catch (IOException ex) { - // Ignore - } - } - - } - - private void clearCache(URLConnection connection) throws IOException { - Object jarFile = ((JarURLConnection) connection).getJarFile(); - if (jarFile instanceof org.springframework.boot.loader.jar.JarFile) { - ((org.springframework.boot.loader.jar.JarFile) jarFile).clearCache(); - } - } - - private static class UseFastConnectionExceptionsEnumeration implements Enumeration { - - private final Enumeration delegate; - - UseFastConnectionExceptionsEnumeration(Enumeration delegate) { - this.delegate = delegate; - } - - @Override - public boolean hasMoreElements() { - Handler.setUseFastConnectionExceptions(true); - try { - return this.delegate.hasMoreElements(); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - - } - - @Override - public URL nextElement() { - Handler.setUseFastConnectionExceptions(true); - try { - return this.delegate.nextElement(); - } - finally { - Handler.setUseFastConnectionExceptions(false); - } - } - - } - - /** - * The different types of call made to define a package. We track these for exploded - * jars so that we can detect packages that should have manifest attributes applied. - */ - private enum DefinePackageCallType { - - /** - * A define package call from a resource that has a manifest. - */ - MANIFEST, - - /** - * A define package call with a direct set of attributes. - */ - ATTRIBUTES - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/Launcher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/Launcher.java deleted file mode 100644 index babaf60dc53..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/Launcher.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.net.URI; -import java.net.URL; -import java.security.CodeSource; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; -import org.springframework.boot.loader.jar.JarFile; - -/** - * Base class for launchers that can start an application with a fully configured - * classpath backed by one or more {@link Archive}s. - * - * @author Phillip Webb - * @author Dave Syer - * @since 1.0.0 - */ -public abstract class Launcher { - - private static final String JAR_MODE_LAUNCHER = "org.springframework.boot.loader.jarmode.JarModeLauncher"; - - /** - * Launch the application. This method is the initial entry point that should be - * called by a subclass {@code public static void main(String[] args)} method. - * @param args the incoming arguments - * @throws Exception if the application fails to launch - */ - protected void launch(String[] args) throws Exception { - if (!isExploded()) { - JarFile.registerUrlProtocolHandler(); - } - ClassLoader classLoader = createClassLoader(getClassPathArchivesIterator()); - String jarMode = System.getProperty("jarmode"); - String launchClass = (jarMode != null && !jarMode.isEmpty()) ? JAR_MODE_LAUNCHER : getMainClass(); - launch(args, launchClass, classLoader); - } - - /** - * Create a classloader for the specified archives. - * @param archives the archives - * @return the classloader - * @throws Exception if the classloader cannot be created - * @since 2.3.0 - */ - protected ClassLoader createClassLoader(Iterator archives) throws Exception { - List urls = new ArrayList<>(50); - while (archives.hasNext()) { - urls.add(archives.next().getUrl()); - } - return createClassLoader(urls.toArray(new URL[0])); - } - - /** - * Create a classloader for the specified URLs. - * @param urls the URLs - * @return the classloader - * @throws Exception if the classloader cannot be created - */ - protected ClassLoader createClassLoader(URL[] urls) throws Exception { - return new LaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader()); - } - - /** - * Launch the application given the archive file and a fully configured classloader. - * @param args the incoming arguments - * @param launchClass the launch class to run - * @param classLoader the classloader - * @throws Exception if the launch fails - */ - protected void launch(String[] args, String launchClass, ClassLoader classLoader) throws Exception { - Thread.currentThread().setContextClassLoader(classLoader); - createMainMethodRunner(launchClass, args, classLoader).run(); - } - - /** - * Create the {@code MainMethodRunner} used to launch the application. - * @param mainClass the main class - * @param args the incoming arguments - * @param classLoader the classloader - * @return the main method runner - */ - protected MainMethodRunner createMainMethodRunner(String mainClass, String[] args, ClassLoader classLoader) { - return new MainMethodRunner(mainClass, args); - } - - /** - * Returns the main class that should be launched. - * @return the name of the main class - * @throws Exception if the main class cannot be obtained - */ - protected abstract String getMainClass() throws Exception; - - /** - * Returns the archives that will be used to construct the class path. - * @return the class path archives - * @throws Exception if the class path archives cannot be obtained - * @since 2.3.0 - */ - protected abstract Iterator getClassPathArchivesIterator() throws Exception; - - protected final Archive createArchive() throws Exception { - ProtectionDomain protectionDomain = getClass().getProtectionDomain(); - CodeSource codeSource = protectionDomain.getCodeSource(); - URI location = (codeSource != null) ? codeSource.getLocation().toURI() : null; - String path = (location != null) ? location.getSchemeSpecificPart() : null; - if (path == null) { - throw new IllegalStateException("Unable to determine code source archive"); - } - File root = new File(path); - if (!root.exists()) { - throw new IllegalStateException("Unable to determine code source archive from " + root); - } - return (root.isDirectory() ? new ExplodedArchive(root) : new JarFileArchive(root)); - } - - /** - * Returns if the launcher is running in an exploded mode. If this method returns - * {@code true} then only regular JARs are supported and the additional URL and - * ClassLoader support infrastructure can be optimized. - * @return if the jar is exploded. - * @since 2.3.0 - */ - protected boolean isExploded() { - return false; - } - - /** - * Return the root archive. - * @return the root archive - * @since 2.3.1 - */ - protected Archive getArchive() { - return null; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/MainMethodRunner.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/MainMethodRunner.java deleted file mode 100644 index ef25dc1490c..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/MainMethodRunner.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.lang.reflect.Method; - -/** - * Utility class that is used by {@link Launcher}s to call a main method. The class - * containing the main method is loaded using the thread context class loader. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class MainMethodRunner { - - private final String mainClassName; - - private final String[] args; - - /** - * Create a new {@link MainMethodRunner} instance. - * @param mainClass the main class - * @param args incoming arguments - */ - public MainMethodRunner(String mainClass, String[] args) { - this.mainClassName = mainClass; - this.args = (args != null) ? args.clone() : null; - } - - public void run() throws Exception { - Class mainClass = Class.forName(this.mainClassName, false, Thread.currentThread().getContextClassLoader()); - Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); - mainMethod.setAccessible(true); - mainMethod.invoke(null, new Object[] { this.args }); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java deleted file mode 100755 index 065d1439d24..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/PropertiesLauncher.java +++ /dev/null @@ -1,726 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Constructor; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.jar.Manifest; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.Archive.Entry; -import org.springframework.boot.loader.archive.Archive.EntryFilter; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; -import org.springframework.boot.loader.util.SystemPropertyUtils; - -/** - * {@link Launcher} for archives with user-configured classpath and main class through a - * properties file. This model is often more flexible and more amenable to creating - * well-behaved OS-level services than a model based on executable jars. - *

- * Looks in various places for a properties file to extract loader settings, defaulting to - * {@code loader.properties} either on the current classpath or in the current working - * directory. The name of the properties file can be changed by setting a System property - * {@code loader.config.name} (e.g. {@code -Dloader.config.name=foo} will look for - * {@code foo.properties}. If that file doesn't exist then tries - * {@code loader.config.location} (with allowed prefixes {@code classpath:} and - * {@code file:} or any valid URL). Once that file is located turns it into Properties and - * extracts optional values (which can also be provided overridden as System properties in - * case the file doesn't exist): - *

    - *
  • {@code loader.path}: a comma-separated list of directories (containing file - * resources and/or nested archives in *.jar or *.zip or archives) or archives to append - * to the classpath. {@code BOOT-INF/classes,BOOT-INF/lib} in the application archive are - * always used
  • - *
  • {@code loader.main}: the main method to delegate execution to once the class loader - * is set up. No default, but will fall back to looking for a {@code Start-Class} in a - * {@code MANIFEST.MF}, if there is one in ${loader.home}/META-INF.
  • - *
- * - * @author Dave Syer - * @author Janne Valkealahti - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class PropertiesLauncher extends Launcher { - - private static final Class[] PARENT_ONLY_PARAMS = new Class[] { ClassLoader.class }; - - private static final Class[] URLS_AND_PARENT_PARAMS = new Class[] { URL[].class, ClassLoader.class }; - - private static final Class[] NO_PARAMS = new Class[] {}; - - private static final URL[] NO_URLS = new URL[0]; - - private static final String DEBUG = "loader.debug"; - - /** - * Properties key for main class. As a manifest entry can also be specified as - * {@code Start-Class}. - */ - public static final String MAIN = "loader.main"; - - /** - * Properties key for classpath entries (directories possibly containing jars or - * jars). Multiple entries can be specified using a comma-separated list. {@code - * BOOT-INF/classes,BOOT-INF/lib} in the application archive are always used. - */ - public static final String PATH = "loader.path"; - - /** - * Properties key for home directory. This is the location of external configuration - * if not on classpath, and also the base path for any relative paths in the - * {@link #PATH loader path}. Defaults to current working directory ( - * ${user.dir}). - */ - public static final String HOME = "loader.home"; - - /** - * Properties key for default command line arguments. These arguments (if present) are - * prepended to the main method arguments before launching. - */ - public static final String ARGS = "loader.args"; - - /** - * Properties key for name of external configuration file (excluding suffix). Defaults - * to "application". Ignored if {@link #CONFIG_LOCATION loader config location} is - * provided instead. - */ - public static final String CONFIG_NAME = "loader.config.name"; - - /** - * Properties key for config file location (including optional classpath:, file: or - * URL prefix). - */ - public static final String CONFIG_LOCATION = "loader.config.location"; - - /** - * Properties key for boolean flag (default false) which, if set, will cause the - * external configuration properties to be copied to System properties (assuming that - * is allowed by Java security). - */ - public static final String SET_SYSTEM_PROPERTIES = "loader.system"; - - private static final Pattern WORD_SEPARATOR = Pattern.compile("\\W+"); - - private static final String NESTED_ARCHIVE_SEPARATOR = "!" + File.separator; - - private final File home; - - private List paths = new ArrayList<>(); - - private final Properties properties = new Properties(); - - private final Archive parent; - - private volatile ClassPathArchives classPathArchives; - - public PropertiesLauncher() { - try { - this.home = getHomeDirectory(); - initializeProperties(); - initializePaths(); - this.parent = createArchive(); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - protected File getHomeDirectory() { - try { - return new File(getPropertyWithDefault(HOME, "${user.dir}")); - } - catch (Exception ex) { - throw new IllegalStateException(ex); - } - } - - private void initializeProperties() throws Exception { - List configs = new ArrayList<>(); - if (getProperty(CONFIG_LOCATION) != null) { - configs.add(getProperty(CONFIG_LOCATION)); - } - else { - String[] names = getPropertyWithDefault(CONFIG_NAME, "loader").split(","); - for (String name : names) { - configs.add("file:" + getHomeDirectory() + "/" + name + ".properties"); - configs.add("classpath:" + name + ".properties"); - configs.add("classpath:BOOT-INF/classes/" + name + ".properties"); - } - } - for (String config : configs) { - try (InputStream resource = getResource(config)) { - if (resource != null) { - debug("Found: " + config); - loadResource(resource); - // Load the first one we find - return; - } - else { - debug("Not found: " + config); - } - } - } - } - - private void loadResource(InputStream resource) throws Exception { - this.properties.load(resource); - for (Object key : Collections.list(this.properties.propertyNames())) { - String text = this.properties.getProperty((String) key); - String value = SystemPropertyUtils.resolvePlaceholders(this.properties, text); - if (value != null) { - this.properties.put(key, value); - } - } - if ("true".equals(getProperty(SET_SYSTEM_PROPERTIES))) { - debug("Adding resolved properties to System properties"); - for (Object key : Collections.list(this.properties.propertyNames())) { - String value = this.properties.getProperty((String) key); - System.setProperty((String) key, value); - } - } - } - - private InputStream getResource(String config) throws Exception { - if (config.startsWith("classpath:")) { - return getClasspathResource(config.substring("classpath:".length())); - } - config = handleUrl(config); - if (isUrl(config)) { - return getURLResource(config); - } - return getFileResource(config); - } - - private String handleUrl(String path) { - if (path.startsWith("jar:file:") || path.startsWith("file:")) { - path = URLDecoder.decode(path, StandardCharsets.UTF_8); - if (path.startsWith("file:")) { - path = path.substring("file:".length()); - if (path.startsWith("//")) { - path = path.substring(2); - } - } - } - return path; - } - - private boolean isUrl(String config) { - return config.contains("://"); - } - - private InputStream getClasspathResource(String config) { - while (config.startsWith("/")) { - config = config.substring(1); - } - config = "/" + config; - debug("Trying classpath: " + config); - return getClass().getResourceAsStream(config); - } - - private InputStream getFileResource(String config) throws Exception { - File file = new File(config); - debug("Trying file: " + config); - if (file.canRead()) { - return new FileInputStream(file); - } - return null; - } - - private InputStream getURLResource(String config) throws Exception { - URL url = new URL(config); - if (exists(url)) { - URLConnection con = url.openConnection(); - try { - return con.getInputStream(); - } - catch (IOException ex) { - // Close the HTTP connection (if applicable). - if (con instanceof HttpURLConnection httpURLConnection) { - httpURLConnection.disconnect(); - } - throw ex; - } - } - return null; - } - - private boolean exists(URL url) throws IOException { - // Try a URL connection content-length header... - URLConnection connection = url.openConnection(); - try { - connection.setUseCaches(connection.getClass().getSimpleName().startsWith("JNLP")); - if (connection instanceof HttpURLConnection httpConnection) { - httpConnection.setRequestMethod("HEAD"); - int responseCode = httpConnection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - return true; - } - else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) { - return false; - } - } - return (connection.getContentLength() >= 0); - } - finally { - if (connection instanceof HttpURLConnection httpURLConnection) { - httpURLConnection.disconnect(); - } - } - } - - private void initializePaths() throws Exception { - String path = getProperty(PATH); - if (path != null) { - this.paths = parsePathsProperty(path); - } - debug("Nested archive paths: " + this.paths); - } - - private List parsePathsProperty(String commaSeparatedPaths) { - List paths = new ArrayList<>(); - for (String path : commaSeparatedPaths.split(",")) { - path = cleanupPath(path); - // "" means the user wants root of archive but not current directory - path = (path == null || path.isEmpty()) ? "/" : path; - paths.add(path); - } - if (paths.isEmpty()) { - paths.add("lib"); - } - return paths; - } - - protected String[] getArgs(String... args) throws Exception { - String loaderArgs = getProperty(ARGS); - if (loaderArgs != null) { - String[] defaultArgs = loaderArgs.split("\\s+"); - String[] additionalArgs = args; - args = new String[defaultArgs.length + additionalArgs.length]; - System.arraycopy(defaultArgs, 0, args, 0, defaultArgs.length); - System.arraycopy(additionalArgs, 0, args, defaultArgs.length, additionalArgs.length); - } - return args; - } - - @Override - protected String getMainClass() throws Exception { - String mainClass = getProperty(MAIN, "Start-Class"); - if (mainClass == null) { - throw new IllegalStateException("No '" + MAIN + "' or 'Start-Class' specified"); - } - return mainClass; - } - - @Override - protected ClassLoader createClassLoader(Iterator archives) throws Exception { - String customLoaderClassName = getProperty("loader.classLoader"); - if (customLoaderClassName == null) { - return super.createClassLoader(archives); - } - Set urls = new LinkedHashSet<>(); - while (archives.hasNext()) { - urls.add(archives.next().getUrl()); - } - ClassLoader loader = new LaunchedURLClassLoader(urls.toArray(NO_URLS), getClass().getClassLoader()); - debug("Classpath for custom loader: " + urls); - loader = wrapWithCustomClassLoader(loader, customLoaderClassName); - debug("Using custom class loader: " + customLoaderClassName); - return loader; - } - - @SuppressWarnings("unchecked") - private ClassLoader wrapWithCustomClassLoader(ClassLoader parent, String className) throws Exception { - Class type = (Class) Class.forName(className, true, parent); - ClassLoader classLoader = newClassLoader(type, PARENT_ONLY_PARAMS, parent); - if (classLoader == null) { - classLoader = newClassLoader(type, URLS_AND_PARENT_PARAMS, NO_URLS, parent); - } - if (classLoader == null) { - classLoader = newClassLoader(type, NO_PARAMS); - } - if (classLoader == null) { - throw new IllegalArgumentException("Unable to create class loader for " + className); - } - return classLoader; - } - - private ClassLoader newClassLoader(Class loaderClass, Class[] parameterTypes, Object... initargs) - throws Exception { - try { - Constructor constructor = loaderClass.getDeclaredConstructor(parameterTypes); - constructor.setAccessible(true); - return constructor.newInstance(initargs); - } - catch (NoSuchMethodException ex) { - return null; - } - } - - private String getProperty(String propertyKey) throws Exception { - return getProperty(propertyKey, null, null); - } - - private String getProperty(String propertyKey, String manifestKey) throws Exception { - return getProperty(propertyKey, manifestKey, null); - } - - private String getPropertyWithDefault(String propertyKey, String defaultValue) throws Exception { - return getProperty(propertyKey, null, defaultValue); - } - - private String getProperty(String propertyKey, String manifestKey, String defaultValue) throws Exception { - if (manifestKey == null) { - manifestKey = propertyKey.replace('.', '-'); - manifestKey = toCamelCase(manifestKey); - } - String property = SystemPropertyUtils.getProperty(propertyKey); - if (property != null) { - String value = SystemPropertyUtils.resolvePlaceholders(this.properties, property); - debug("Property '" + propertyKey + "' from environment: " + value); - return value; - } - if (this.properties.containsKey(propertyKey)) { - String value = SystemPropertyUtils.resolvePlaceholders(this.properties, - this.properties.getProperty(propertyKey)); - debug("Property '" + propertyKey + "' from properties: " + value); - return value; - } - try { - if (this.home != null) { - // Prefer home dir for MANIFEST if there is one - try (ExplodedArchive archive = new ExplodedArchive(this.home, false)) { - Manifest manifest = archive.getManifest(); - if (manifest != null) { - String value = manifest.getMainAttributes().getValue(manifestKey); - if (value != null) { - debug("Property '" + manifestKey + "' from home directory manifest: " + value); - return SystemPropertyUtils.resolvePlaceholders(this.properties, value); - } - } - } - } - } - catch (IllegalStateException ex) { - // Ignore - } - // Otherwise try the parent archive - Manifest manifest = createArchive().getManifest(); - if (manifest != null) { - String value = manifest.getMainAttributes().getValue(manifestKey); - if (value != null) { - debug("Property '" + manifestKey + "' from archive manifest: " + value); - return SystemPropertyUtils.resolvePlaceholders(this.properties, value); - } - } - return (defaultValue != null) ? SystemPropertyUtils.resolvePlaceholders(this.properties, defaultValue) - : defaultValue; - } - - @Override - protected Iterator getClassPathArchivesIterator() throws Exception { - ClassPathArchives classPathArchives = this.classPathArchives; - if (classPathArchives == null) { - classPathArchives = new ClassPathArchives(); - this.classPathArchives = classPathArchives; - } - return classPathArchives.iterator(); - } - - public static void main(String[] args) throws Exception { - PropertiesLauncher launcher = new PropertiesLauncher(); - args = launcher.getArgs(args); - launcher.launch(args); - } - - public static String toCamelCase(CharSequence string) { - if (string == null) { - return null; - } - StringBuilder builder = new StringBuilder(); - Matcher matcher = WORD_SEPARATOR.matcher(string); - int pos = 0; - while (matcher.find()) { - builder.append(capitalize(string.subSequence(pos, matcher.end()).toString())); - pos = matcher.end(); - } - builder.append(capitalize(string.subSequence(pos, string.length()).toString())); - return builder.toString(); - } - - private static String capitalize(String str) { - return Character.toUpperCase(str.charAt(0)) + str.substring(1); - } - - private void debug(String message) { - if (Boolean.getBoolean(DEBUG)) { - System.out.println(message); - } - } - - private String cleanupPath(String path) { - path = path.trim(); - // No need for current dir path - if (path.startsWith("./")) { - path = path.substring(2); - } - String lowerCasePath = path.toLowerCase(Locale.ENGLISH); - if (lowerCasePath.endsWith(".jar") || lowerCasePath.endsWith(".zip")) { - return path; - } - if (path.endsWith("/*")) { - path = path.substring(0, path.length() - 1); - } - else { - // It's a directory - if (!path.endsWith("/") && !path.equals(".")) { - path = path + "/"; - } - } - return path; - } - - void close() throws Exception { - if (this.classPathArchives != null) { - this.classPathArchives.close(); - } - if (this.parent != null) { - this.parent.close(); - } - } - - /** - * An iterable collection of the classpath archives. - */ - private class ClassPathArchives implements Iterable { - - private final List classPathArchives; - - private final List jarFileArchives = new ArrayList<>(); - - ClassPathArchives() throws Exception { - this.classPathArchives = new ArrayList<>(); - for (String path : PropertiesLauncher.this.paths) { - for (Archive archive : getClassPathArchives(path)) { - addClassPathArchive(archive); - } - } - addNestedEntries(); - } - - private void addClassPathArchive(Archive archive) throws IOException { - if (!(archive instanceof ExplodedArchive)) { - this.classPathArchives.add(archive); - return; - } - this.classPathArchives.add(archive); - this.classPathArchives.addAll(asList(archive.getNestedArchives(null, new ArchiveEntryFilter()))); - } - - private List getClassPathArchives(String path) throws Exception { - String root = cleanupPath(handleUrl(path)); - List lib = new ArrayList<>(); - File file = new File(root); - if (!"/".equals(root)) { - if (!isAbsolutePath(root)) { - file = new File(PropertiesLauncher.this.home, root); - } - if (file.isDirectory()) { - debug("Adding classpath entries from " + file); - Archive archive = new ExplodedArchive(file, false); - lib.add(archive); - } - } - Archive archive = getArchive(file); - if (archive != null) { - debug("Adding classpath entries from archive " + archive.getUrl() + root); - lib.add(archive); - } - List nestedArchives = getNestedArchives(root); - if (nestedArchives != null) { - debug("Adding classpath entries from nested " + root); - lib.addAll(nestedArchives); - } - return lib; - } - - private boolean isAbsolutePath(String root) { - // Windows contains ":" others start with "/" - return root.contains(":") || root.startsWith("/"); - } - - private Archive getArchive(File file) throws IOException { - if (isNestedArchivePath(file)) { - return null; - } - String name = file.getName().toLowerCase(Locale.ENGLISH); - if (name.endsWith(".jar") || name.endsWith(".zip")) { - return getJarFileArchive(file); - } - return null; - } - - private boolean isNestedArchivePath(File file) { - return file.getPath().contains(NESTED_ARCHIVE_SEPARATOR); - } - - private List getNestedArchives(String path) throws Exception { - Archive parent = PropertiesLauncher.this.parent; - String root = path; - if (!root.equals("/") && root.startsWith("/") - || parent.getUrl().toURI().equals(PropertiesLauncher.this.home.toURI())) { - // If home dir is same as parent archive, no need to add it twice. - return null; - } - int index = root.indexOf('!'); - if (index != -1) { - File file = new File(PropertiesLauncher.this.home, root.substring(0, index)); - if (root.startsWith("jar:file:")) { - file = new File(root.substring("jar:file:".length(), index)); - } - parent = getJarFileArchive(file); - root = root.substring(index + 1); - while (root.startsWith("/")) { - root = root.substring(1); - } - } - if (root.endsWith(".jar")) { - File file = new File(PropertiesLauncher.this.home, root); - if (file.exists()) { - parent = getJarFileArchive(file); - root = ""; - } - } - if (root.equals("/") || root.equals("./") || root.equals(".")) { - // The prefix for nested jars is actually empty if it's at the root - root = ""; - } - EntryFilter filter = new PrefixMatchingArchiveFilter(root); - List archives = asList(parent.getNestedArchives(null, filter)); - if ((root == null || root.isEmpty() || ".".equals(root)) && !path.endsWith(".jar") - && parent != PropertiesLauncher.this.parent) { - // You can't find the root with an entry filter so it has to be added - // explicitly. But don't add the root of the parent archive. - archives.add(parent); - } - return archives; - } - - private void addNestedEntries() { - // The parent archive might have "BOOT-INF/lib/" and "BOOT-INF/classes/" - // directories, meaning we are running from an executable JAR. We add nested - // entries from there with low priority (i.e. at end). - try { - Iterator archives = PropertiesLauncher.this.parent.getNestedArchives(null, - JarLauncher.NESTED_ARCHIVE_ENTRY_FILTER); - while (archives.hasNext()) { - this.classPathArchives.add(archives.next()); - } - } - catch (IOException ex) { - // Ignore - } - } - - private List asList(Iterator iterator) { - List list = new ArrayList<>(); - while (iterator.hasNext()) { - list.add(iterator.next()); - } - return list; - } - - private JarFileArchive getJarFileArchive(File file) throws IOException { - JarFileArchive archive = new JarFileArchive(file); - this.jarFileArchives.add(archive); - return archive; - } - - @Override - public Iterator iterator() { - return this.classPathArchives.iterator(); - } - - void close() throws IOException { - for (JarFileArchive archive : this.jarFileArchives) { - archive.close(); - } - } - - } - - /** - * Convenience class for finding nested archives that have a prefix in their file path - * (e.g. "lib/"). - */ - private static final class PrefixMatchingArchiveFilter implements EntryFilter { - - private final String prefix; - - private final ArchiveEntryFilter filter = new ArchiveEntryFilter(); - - private PrefixMatchingArchiveFilter(String prefix) { - this.prefix = prefix; - } - - @Override - public boolean matches(Entry entry) { - if (entry.isDirectory()) { - return entry.getName().equals(this.prefix); - } - return entry.getName().startsWith(this.prefix) && this.filter.matches(entry); - } - - } - - /** - * Convenience class for finding nested archives (archive entries that can be - * classpath entries). - */ - private static final class ArchiveEntryFilter implements EntryFilter { - - private static final String DOT_JAR = ".jar"; - - private static final String DOT_ZIP = ".zip"; - - @Override - public boolean matches(Entry entry) { - return entry.getName().endsWith(DOT_JAR) || entry.getName().endsWith(DOT_ZIP); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/WarLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/WarLauncher.java deleted file mode 100644 index 654a9046d3f..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/WarLauncher.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import org.springframework.boot.loader.archive.Archive; - -/** - * {@link Launcher} for WAR based archives. This launcher for standard WAR archives. - * Supports dependencies in {@code WEB-INF/lib} as well as {@code WEB-INF/lib-provided}, - * classes are loaded from {@code WEB-INF/classes}. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Scott Frederick - * @since 1.0.0 - */ -public class WarLauncher extends ExecutableArchiveLauncher { - - public WarLauncher() { - } - - protected WarLauncher(Archive archive) { - super(archive); - } - - @Override - protected boolean isPostProcessingClassPathArchives() { - return false; - } - - @Override - public boolean isNestedArchive(Archive.Entry entry) { - if (entry.isDirectory()) { - return entry.getName().equals("WEB-INF/classes/"); - } - return entry.getName().startsWith("WEB-INF/lib/") || entry.getName().startsWith("WEB-INF/lib-provided/"); - } - - @Override - protected String getArchiveEntryPathPrefix() { - return "WEB-INF/"; - } - - public static void main(String[] args) throws Exception { - new WarLauncher().launch(args); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/Archive.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/Archive.java deleted file mode 100644 index 84ed7e1d982..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/Archive.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.archive; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Iterator; -import java.util.jar.Manifest; - -import org.springframework.boot.loader.Launcher; - -/** - * An archive that can be launched by the {@link Launcher}. - * - * @author Phillip Webb - * @since 1.0.0 - * @see JarFileArchive - */ -public interface Archive extends Iterable, AutoCloseable { - - /** - * Returns a URL that can be used to load the archive. - * @return the archive URL - * @throws MalformedURLException if the URL is malformed - */ - URL getUrl() throws MalformedURLException; - - /** - * Returns the manifest of the archive. - * @return the manifest - * @throws IOException if the manifest cannot be read - */ - Manifest getManifest() throws IOException; - - /** - * Returns nested {@link Archive}s for entries that match the specified filters. - * @param searchFilter filter used to limit when additional sub-entry searching is - * required or {@code null} if all entries should be considered. - * @param includeFilter filter used to determine which entries should be included in - * the result or {@code null} if all entries should be included - * @return the nested archives - * @throws IOException on IO error - * @since 2.3.0 - */ - Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException; - - /** - * Return if the archive is exploded (already unpacked). - * @return if the archive is exploded - * @since 2.3.0 - */ - default boolean isExploded() { - return false; - } - - /** - * Closes the {@code Archive}, releasing any open resources. - * @throws Exception if an error occurs during close processing - * @since 2.2.0 - */ - @Override - default void close() throws Exception { - - } - - /** - * Represents a single entry in the archive. - */ - interface Entry { - - /** - * Returns {@code true} if the entry represents a directory. - * @return if the entry is a directory - */ - boolean isDirectory(); - - /** - * Returns the name of the entry. - * @return the name of the entry - */ - String getName(); - - } - - /** - * Strategy interface to filter {@link Entry Entries}. - */ - @FunctionalInterface - interface EntryFilter { - - /** - * Apply the jar entry filter. - * @param entry the entry to filter - * @return {@code true} if the filter matches - */ - boolean matches(Entry entry); - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java deleted file mode 100644 index 5fc62ff8836..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/ExplodedArchive.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.archive; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Deque; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.jar.Manifest; - -/** - * {@link Archive} implementation backed by an exploded archive directory. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Madhura Bhave - * @since 1.0.0 - */ -public class ExplodedArchive implements Archive { - - private static final Set SKIPPED_NAMES = new HashSet<>(Arrays.asList(".", "..")); - - private final File root; - - private final boolean recursive; - - private final File manifestFile; - - private Manifest manifest; - - /** - * Create a new {@link ExplodedArchive} instance. - * @param root the root directory - */ - public ExplodedArchive(File root) { - this(root, true); - } - - /** - * Create a new {@link ExplodedArchive} instance. - * @param root the root directory - * @param recursive if recursive searching should be used to locate the manifest. - * Defaults to {@code true}, directories with a large tree might want to set this to - * {@code false}. - */ - public ExplodedArchive(File root, boolean recursive) { - if (!root.exists() || !root.isDirectory()) { - throw new IllegalArgumentException("Invalid source directory " + root); - } - this.root = root; - this.recursive = recursive; - this.manifestFile = getManifestFile(root); - } - - private File getManifestFile(File root) { - File metaInf = new File(root, "META-INF"); - return new File(metaInf, "MANIFEST.MF"); - } - - @Override - public URL getUrl() throws MalformedURLException { - return this.root.toURI().toURL(); - } - - @Override - public Manifest getManifest() throws IOException { - if (this.manifest == null && this.manifestFile.exists()) { - try (FileInputStream inputStream = new FileInputStream(this.manifestFile)) { - this.manifest = new Manifest(inputStream); - } - } - return this.manifest; - } - - @Override - public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException { - return new ArchiveIterator(this.root, this.recursive, searchFilter, includeFilter); - } - - @Override - @Deprecated(since = "2.3.10", forRemoval = false) - public Iterator iterator() { - return new EntryIterator(this.root, this.recursive, null, null); - } - - protected Archive getNestedArchive(Entry entry) { - File file = ((FileEntry) entry).getFile(); - return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive((FileEntry) entry)); - } - - @Override - public boolean isExploded() { - return true; - } - - @Override - public String toString() { - try { - return getUrl().toString(); - } - catch (Exception ex) { - return "exploded archive"; - } - } - - /** - * File based {@link Entry} {@link Iterator}. - */ - private abstract static class AbstractIterator implements Iterator { - - private static final Comparator entryComparator = Comparator.comparing(File::getAbsolutePath); - - private final File root; - - private final boolean recursive; - - private final EntryFilter searchFilter; - - private final EntryFilter includeFilter; - - private final Deque> stack = new LinkedList<>(); - - private FileEntry current; - - private final String rootUrl; - - AbstractIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { - this.root = root; - this.rootUrl = this.root.toURI().getPath(); - this.recursive = recursive; - this.searchFilter = searchFilter; - this.includeFilter = includeFilter; - this.stack.add(listFiles(root)); - this.current = poll(); - } - - @Override - public boolean hasNext() { - return this.current != null; - } - - @Override - public T next() { - FileEntry entry = this.current; - if (entry == null) { - throw new NoSuchElementException(); - } - this.current = poll(); - return adapt(entry); - } - - private FileEntry poll() { - while (!this.stack.isEmpty()) { - while (this.stack.peek().hasNext()) { - File file = this.stack.peek().next(); - if (SKIPPED_NAMES.contains(file.getName())) { - continue; - } - FileEntry entry = getFileEntry(file); - if (isListable(entry)) { - this.stack.addFirst(listFiles(file)); - } - if (this.includeFilter == null || this.includeFilter.matches(entry)) { - return entry; - } - } - this.stack.poll(); - } - return null; - } - - private FileEntry getFileEntry(File file) { - URI uri = file.toURI(); - String name = uri.getPath().substring(this.rootUrl.length()); - try { - return new FileEntry(name, file, uri.toURL()); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - private boolean isListable(FileEntry entry) { - return entry.isDirectory() && (this.recursive || entry.getFile().getParentFile().equals(this.root)) - && (this.searchFilter == null || this.searchFilter.matches(entry)) - && (this.includeFilter == null || !this.includeFilter.matches(entry)); - } - - private Iterator listFiles(File file) { - File[] files = file.listFiles(); - if (files == null) { - return Collections.emptyIterator(); - } - Arrays.sort(files, entryComparator); - return Arrays.asList(files).iterator(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - - protected abstract T adapt(FileEntry entry); - - } - - private static class EntryIterator extends AbstractIterator { - - EntryIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { - super(root, recursive, searchFilter, includeFilter); - } - - @Override - protected Entry adapt(FileEntry entry) { - return entry; - } - - } - - private static class ArchiveIterator extends AbstractIterator { - - ArchiveIterator(File root, boolean recursive, EntryFilter searchFilter, EntryFilter includeFilter) { - super(root, recursive, searchFilter, includeFilter); - } - - @Override - protected Archive adapt(FileEntry entry) { - File file = entry.getFile(); - return (file.isDirectory() ? new ExplodedArchive(file) : new SimpleJarFileArchive(entry)); - } - - } - - /** - * {@link Entry} backed by a File. - */ - private static class FileEntry implements Entry { - - private final String name; - - private final File file; - - private final URL url; - - FileEntry(String name, File file, URL url) { - this.name = name; - this.file = file; - this.url = url; - } - - File getFile() { - return this.file; - } - - @Override - public boolean isDirectory() { - return this.file.isDirectory(); - } - - @Override - public String getName() { - return this.name; - } - - URL getUrl() { - return this.url; - } - - } - - /** - * {@link Archive} implementation backed by a simple JAR file that doesn't itself - * contain nested archives. - */ - private static class SimpleJarFileArchive implements Archive { - - private final URL url; - - SimpleJarFileArchive(FileEntry file) { - this.url = file.getUrl(); - } - - @Override - public URL getUrl() throws MalformedURLException { - return this.url; - } - - @Override - public Manifest getManifest() throws IOException { - return null; - } - - @Override - public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) - throws IOException { - return Collections.emptyIterator(); - } - - @Override - @Deprecated(since = "2.3.10", forRemoval = false) - public Iterator iterator() { - return Collections.emptyIterator(); - } - - @Override - public String toString() { - try { - return getUrl().toString(); - } - catch (Exception ex) { - return "jar archive"; - } - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java deleted file mode 100755 index 7574cd66bd5..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/JarFileArchive.java +++ /dev/null @@ -1,311 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.archive; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.util.EnumSet; -import java.util.Iterator; -import java.util.UUID; -import java.util.jar.JarEntry; -import java.util.jar.Manifest; - -import org.springframework.boot.loader.jar.JarFile; - -/** - * {@link Archive} implementation backed by a {@link JarFile}. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class JarFileArchive implements Archive { - - private static final String UNPACK_MARKER = "UNPACK:"; - - private static final int BUFFER_SIZE = 32 * 1024; - - private static final FileAttribute[] NO_FILE_ATTRIBUTES = {}; - - private static final EnumSet DIRECTORY_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ, - PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE); - - private static final EnumSet FILE_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ, - PosixFilePermission.OWNER_WRITE); - - private final JarFile jarFile; - - private URL url; - - private Path tempUnpackDirectory; - - public JarFileArchive(File file) throws IOException { - this(file, file.toURI().toURL()); - } - - public JarFileArchive(File file, URL url) throws IOException { - this(new JarFile(file)); - this.url = url; - } - - public JarFileArchive(JarFile jarFile) { - this.jarFile = jarFile; - } - - @Override - public URL getUrl() throws MalformedURLException { - if (this.url != null) { - return this.url; - } - return this.jarFile.getUrl(); - } - - @Override - public Manifest getManifest() throws IOException { - return this.jarFile.getManifest(); - } - - @Override - public Iterator getNestedArchives(EntryFilter searchFilter, EntryFilter includeFilter) throws IOException { - return new NestedArchiveIterator(this.jarFile.iterator(), searchFilter, includeFilter); - } - - @Override - @Deprecated(since = "2.3.10", forRemoval = false) - public Iterator iterator() { - return new EntryIterator(this.jarFile.iterator(), null, null); - } - - @Override - public void close() throws IOException { - this.jarFile.close(); - } - - protected Archive getNestedArchive(Entry entry) throws IOException { - JarEntry jarEntry = ((JarFileEntry) entry).getJarEntry(); - if (jarEntry.getComment().startsWith(UNPACK_MARKER)) { - return getUnpackedNestedArchive(jarEntry); - } - try { - JarFile jarFile = this.jarFile.getNestedJarFile(jarEntry); - return new JarFileArchive(jarFile); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to get nested archive for entry " + entry.getName(), ex); - } - } - - private Archive getUnpackedNestedArchive(JarEntry jarEntry) throws IOException { - String name = jarEntry.getName(); - if (name.lastIndexOf('/') != -1) { - name = name.substring(name.lastIndexOf('/') + 1); - } - Path path = getTempUnpackDirectory().resolve(name); - if (!Files.exists(path) || Files.size(path) != jarEntry.getSize()) { - unpack(jarEntry, path); - } - return new JarFileArchive(path.toFile(), path.toUri().toURL()); - } - - private Path getTempUnpackDirectory() { - if (this.tempUnpackDirectory == null) { - Path tempDirectory = Paths.get(System.getProperty("java.io.tmpdir")); - this.tempUnpackDirectory = createUnpackDirectory(tempDirectory); - } - return this.tempUnpackDirectory; - } - - private Path createUnpackDirectory(Path parent) { - int attempts = 0; - while (attempts++ < 1000) { - String fileName = Paths.get(this.jarFile.getName()).getFileName().toString(); - Path unpackDirectory = parent.resolve(fileName + "-spring-boot-libs-" + UUID.randomUUID()); - try { - createDirectory(unpackDirectory); - return unpackDirectory; - } - catch (IOException ex) { - // Ignore - } - } - throw new IllegalStateException("Failed to create unpack directory in directory '" + parent + "'"); - } - - private void unpack(JarEntry entry, Path path) throws IOException { - createFile(path); - path.toFile().deleteOnExit(); - try (InputStream inputStream = this.jarFile.getInputStream(entry); - OutputStream outputStream = Files.newOutputStream(path, StandardOpenOption.WRITE, - StandardOpenOption.TRUNCATE_EXISTING)) { - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - outputStream.flush(); - } - } - - private void createDirectory(Path path) throws IOException { - Files.createDirectory(path, getFileAttributes(path.getFileSystem(), DIRECTORY_PERMISSIONS)); - } - - private void createFile(Path path) throws IOException { - Files.createFile(path, getFileAttributes(path.getFileSystem(), FILE_PERMISSIONS)); - } - - private FileAttribute[] getFileAttributes(FileSystem fileSystem, EnumSet ownerReadWrite) { - if (!fileSystem.supportedFileAttributeViews().contains("posix")) { - return NO_FILE_ATTRIBUTES; - } - return new FileAttribute[] { PosixFilePermissions.asFileAttribute(ownerReadWrite) }; - } - - @Override - public String toString() { - try { - return getUrl().toString(); - } - catch (Exception ex) { - return "jar archive"; - } - } - - /** - * Abstract base class for iterator implementations. - */ - private abstract static class AbstractIterator implements Iterator { - - private final Iterator iterator; - - private final EntryFilter searchFilter; - - private final EntryFilter includeFilter; - - private Entry current; - - AbstractIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { - this.iterator = iterator; - this.searchFilter = searchFilter; - this.includeFilter = includeFilter; - this.current = poll(); - } - - @Override - public boolean hasNext() { - return this.current != null; - } - - @Override - public T next() { - T result = adapt(this.current); - this.current = poll(); - return result; - } - - private Entry poll() { - while (this.iterator.hasNext()) { - JarFileEntry candidate = new JarFileEntry(this.iterator.next()); - if ((this.searchFilter == null || this.searchFilter.matches(candidate)) - && (this.includeFilter == null || this.includeFilter.matches(candidate))) { - return candidate; - } - } - return null; - } - - protected abstract T adapt(Entry entry); - - } - - /** - * {@link Archive.Entry} iterator implementation backed by {@link JarEntry}. - */ - private static class EntryIterator extends AbstractIterator { - - EntryIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { - super(iterator, searchFilter, includeFilter); - } - - @Override - protected Entry adapt(Entry entry) { - return entry; - } - - } - - /** - * Nested {@link Archive} iterator implementation backed by {@link JarEntry}. - */ - private class NestedArchiveIterator extends AbstractIterator { - - NestedArchiveIterator(Iterator iterator, EntryFilter searchFilter, EntryFilter includeFilter) { - super(iterator, searchFilter, includeFilter); - } - - @Override - protected Archive adapt(Entry entry) { - try { - return getNestedArchive(entry); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - } - - /** - * {@link Archive.Entry} implementation backed by a {@link JarEntry}. - */ - private static class JarFileEntry implements Entry { - - private final JarEntry jarEntry; - - JarFileEntry(JarEntry jarEntry) { - this.jarEntry = jarEntry; - } - - JarEntry getJarEntry() { - return this.jarEntry; - } - - @Override - public boolean isDirectory() { - return this.jarEntry.isDirectory(); - } - - @Override - public String getName() { - return this.jarEntry.getName(); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/package-info.java deleted file mode 100644 index 973ea625507..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/archive/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Abstraction over logical Archives be they backed by a JAR file or unpacked into a - * directory. - * - * @see org.springframework.boot.loader.archive.Archive - */ -package org.springframework.boot.loader.archive; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java deleted file mode 100644 index 9d6c249b872..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.data; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -/** - * Interface that provides read-only random access to some underlying data. - * Implementations must allow concurrent reads in a thread-safe manner. - * - * @author Phillip Webb - * @since 1.0.0 - */ -public interface RandomAccessData { - - /** - * Returns an {@link InputStream} that can be used to read the underlying data. The - * caller is responsible close the underlying stream. - * @return a new input stream that can be used to read the underlying data. - * @throws IOException if the stream cannot be opened - */ - InputStream getInputStream() throws IOException; - - /** - * Returns a new {@link RandomAccessData} for a specific subsection of this data. - * @param offset the offset of the subsection - * @param length the length of the subsection - * @return the subsection data - */ - RandomAccessData getSubsection(long offset, long length); - - /** - * Reads all the data and returns it as a byte array. - * @return the data - * @throws IOException if the data cannot be read - */ - byte[] read() throws IOException; - - /** - * Reads the {@code length} bytes of data starting at the given {@code offset}. - * @param offset the offset from which data should be read - * @param length the number of bytes to be read - * @return the data - * @throws IOException if the data cannot be read - * @throws IndexOutOfBoundsException if offset is beyond the end of the file or - * subsection - * @throws EOFException if offset plus length is greater than the length of the file - * or subsection - */ - byte[] read(long offset, long length) throws IOException; - - /** - * Returns the size of the data. - * @return the size - */ - long getSize(); - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java deleted file mode 100644 index 4ea78334828..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.data; - -import java.io.EOFException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; - -/** - * {@link RandomAccessData} implementation backed by a {@link RandomAccessFile}. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class RandomAccessDataFile implements RandomAccessData { - - private final FileAccess fileAccess; - - private final long offset; - - private final long length; - - /** - * Create a new {@link RandomAccessDataFile} backed by the specified file. - * @param file the underlying file - * @throws IllegalArgumentException if the file is null or does not exist - */ - public RandomAccessDataFile(File file) { - if (file == null) { - throw new IllegalArgumentException("File must not be null"); - } - this.fileAccess = new FileAccess(file); - this.offset = 0L; - this.length = file.length(); - } - - /** - * Private constructor used to create a {@link #getSubsection(long, long) subsection}. - * @param fileAccess provides access to the underlying file - * @param offset the offset of the section - * @param length the length of the section - */ - private RandomAccessDataFile(FileAccess fileAccess, long offset, long length) { - this.fileAccess = fileAccess; - this.offset = offset; - this.length = length; - } - - /** - * Returns the underlying File. - * @return the underlying file - */ - public File getFile() { - return this.fileAccess.file; - } - - @Override - public InputStream getInputStream() throws IOException { - return new DataInputStream(); - } - - @Override - public RandomAccessData getSubsection(long offset, long length) { - if (offset < 0 || length < 0 || offset + length > this.length) { - throw new IndexOutOfBoundsException(); - } - return new RandomAccessDataFile(this.fileAccess, this.offset + offset, length); - } - - @Override - public byte[] read() throws IOException { - return read(0, this.length); - } - - @Override - public byte[] read(long offset, long length) throws IOException { - if (offset > this.length) { - throw new IndexOutOfBoundsException(); - } - if (offset + length > this.length) { - throw new EOFException(); - } - byte[] bytes = new byte[(int) length]; - read(bytes, offset, 0, bytes.length); - return bytes; - } - - private int readByte(long position) throws IOException { - if (position >= this.length) { - return -1; - } - return this.fileAccess.readByte(this.offset + position); - } - - private int read(byte[] bytes, long position, int offset, int length) throws IOException { - if (position > this.length) { - return -1; - } - return this.fileAccess.read(bytes, this.offset + position, offset, length); - } - - @Override - public long getSize() { - return this.length; - } - - public void close() throws IOException { - this.fileAccess.close(); - } - - /** - * {@link InputStream} implementation for the {@link RandomAccessDataFile}. - */ - private final class DataInputStream extends InputStream { - - private int position; - - @Override - public int read() throws IOException { - int read = RandomAccessDataFile.this.readByte(this.position); - if (read > -1) { - moveOn(1); - } - return read; - } - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, (b != null) ? b.length : 0); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (b == null) { - throw new NullPointerException("Bytes must not be null"); - } - return doRead(b, off, len); - } - - /** - * Perform the actual read. - * @param b the bytes to read or {@code null} when reading a single byte - * @param off the offset of the byte array - * @param len the length of data to read - * @return the number of bytes read into {@code b} or the actual read byte if - * {@code b} is {@code null}. Returns -1 when the end of the stream is reached - * @throws IOException in case of I/O errors - */ - int doRead(byte[] b, int off, int len) throws IOException { - if (len == 0) { - return 0; - } - int cappedLen = cap(len); - if (cappedLen <= 0) { - return -1; - } - return (int) moveOn(RandomAccessDataFile.this.read(b, this.position, off, cappedLen)); - } - - @Override - public long skip(long n) throws IOException { - return (n <= 0) ? 0 : moveOn(cap(n)); - } - - @Override - public int available() throws IOException { - return (int) RandomAccessDataFile.this.length - this.position; - } - - /** - * Cap the specified value such that it cannot exceed the number of bytes - * remaining. - * @param n the value to cap - * @return the capped value - */ - private int cap(long n) { - return (int) Math.min(RandomAccessDataFile.this.length - this.position, n); - } - - /** - * Move the stream position forwards the specified amount. - * @param amount the amount to move - * @return the amount moved - */ - private long moveOn(int amount) { - this.position += amount; - return amount; - } - - } - - private static final class FileAccess { - - private final Object monitor = new Object(); - - private final File file; - - private RandomAccessFile randomAccessFile; - - private FileAccess(File file) { - this.file = file; - openIfNecessary(); - } - - private int read(byte[] bytes, long position, int offset, int length) throws IOException { - synchronized (this.monitor) { - openIfNecessary(); - this.randomAccessFile.seek(position); - return this.randomAccessFile.read(bytes, offset, length); - } - } - - private void openIfNecessary() { - if (this.randomAccessFile == null) { - try { - this.randomAccessFile = new RandomAccessFile(this.file, "r"); - } - catch (FileNotFoundException ex) { - throw new IllegalArgumentException( - String.format("File %s must exist", this.file.getAbsolutePath())); - } - } - } - - private void close() throws IOException { - synchronized (this.monitor) { - if (this.randomAccessFile != null) { - this.randomAccessFile.close(); - this.randomAccessFile = null; - } - } - } - - private int readByte(long position) throws IOException { - synchronized (this.monitor) { - openIfNecessary(); - this.randomAccessFile.seek(position); - return this.randomAccessFile.read(); - } - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/package-info.java deleted file mode 100644 index 6b2870c20d9..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/data/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Classes and interfaces to allow random access to a block of data. - * - * @see org.springframework.boot.loader.data.RandomAccessData - */ -package org.springframework.boot.loader.data; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AbstractJarFile.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AbstractJarFile.java deleted file mode 100644 index 56c1032c100..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AbstractJarFile.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.Permission; - -/** - * Base class for extended variants of {@link java.util.jar.JarFile}. - * - * @author Phillip Webb - */ -abstract class AbstractJarFile extends java.util.jar.JarFile { - - /** - * Create a new {@link AbstractJarFile}. - * @param file the root jar file. - * @throws IOException on IO error - */ - AbstractJarFile(File file) throws IOException { - super(file); - } - - /** - * Return a URL that can be used to access this JAR file. NOTE: the specified URL - * cannot be serialized and or cloned. - * @return the URL - * @throws MalformedURLException if the URL is malformed - */ - abstract URL getUrl() throws MalformedURLException; - - /** - * Return the {@link JarFileType} of this instance. - * @return the jar file type - */ - abstract JarFileType getType(); - - /** - * Return the security permission for this JAR. - * @return the security permission. - */ - abstract Permission getPermission(); - - /** - * Return an {@link InputStream} for the entire jar contents. - * @return the contents input stream - * @throws IOException on IO error - */ - abstract InputStream getInputStream() throws IOException; - - /** - * The type of a {@link JarFile}. - */ - enum JarFileType { - - DIRECT, NESTED_DIRECTORY, NESTED_JAR - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java deleted file mode 100644 index b1dbf60e47a..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/AsciiBytes.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.nio.charset.StandardCharsets; - -/** - * Simple wrapper around a byte array that represents an ASCII. Used for performance - * reasons to save constructing Strings for ZIP data. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -final class AsciiBytes { - - private static final String EMPTY_STRING = ""; - - private static final int[] INITIAL_BYTE_BITMASK = { 0x7F, 0x1F, 0x0F, 0x07 }; - - private static final int SUBSEQUENT_BYTE_BITMASK = 0x3F; - - private final byte[] bytes; - - private final int offset; - - private final int length; - - private String string; - - private int hash; - - /** - * Create a new {@link AsciiBytes} from the specified String. - * @param string the source string - */ - AsciiBytes(String string) { - this(string.getBytes(StandardCharsets.UTF_8)); - this.string = string; - } - - /** - * Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes - * are not expected to change. - * @param bytes the source bytes - */ - AsciiBytes(byte[] bytes) { - this(bytes, 0, bytes.length); - } - - /** - * Create a new {@link AsciiBytes} from the specified bytes. NOTE: underlying bytes - * are not expected to change. - * @param bytes the source bytes - * @param offset the offset - * @param length the length - */ - AsciiBytes(byte[] bytes, int offset, int length) { - if (offset < 0 || length < 0 || (offset + length) > bytes.length) { - throw new IndexOutOfBoundsException(); - } - this.bytes = bytes; - this.offset = offset; - this.length = length; - } - - int length() { - return this.length; - } - - boolean startsWith(AsciiBytes prefix) { - if (this == prefix) { - return true; - } - if (prefix.length > this.length) { - return false; - } - for (int i = 0; i < prefix.length; i++) { - if (this.bytes[i + this.offset] != prefix.bytes[i + prefix.offset]) { - return false; - } - } - return true; - } - - boolean endsWith(AsciiBytes postfix) { - if (this == postfix) { - return true; - } - if (postfix.length > this.length) { - return false; - } - for (int i = 0; i < postfix.length; i++) { - if (this.bytes[this.offset + (this.length - 1) - i] != postfix.bytes[postfix.offset + (postfix.length - 1) - - i]) { - return false; - } - } - return true; - } - - AsciiBytes substring(int beginIndex) { - return substring(beginIndex, this.length); - } - - AsciiBytes substring(int beginIndex, int endIndex) { - int length = endIndex - beginIndex; - if (this.offset + length > this.bytes.length) { - throw new IndexOutOfBoundsException(); - } - return new AsciiBytes(this.bytes, this.offset + beginIndex, length); - } - - boolean matches(CharSequence name, char suffix) { - int charIndex = 0; - int nameLen = name.length(); - int totalLen = nameLen + ((suffix != 0) ? 1 : 0); - for (int i = this.offset; i < this.offset + this.length; i++) { - int b = this.bytes[i]; - int remainingUtfBytes = getNumberOfUtfBytes(b) - 1; - b &= INITIAL_BYTE_BITMASK[remainingUtfBytes]; - for (int j = 0; j < remainingUtfBytes; j++) { - b = (b << 6) + (this.bytes[++i] & SUBSEQUENT_BYTE_BITMASK); - } - char c = getChar(name, suffix, charIndex++); - if (b <= 0xFFFF) { - if (c != b) { - return false; - } - } - else { - if (c != ((b >> 0xA) + 0xD7C0)) { - return false; - } - c = getChar(name, suffix, charIndex++); - if (c != ((b & 0x3FF) + 0xDC00)) { - return false; - } - } - } - return charIndex == totalLen; - } - - private char getChar(CharSequence name, char suffix, int index) { - if (index < name.length()) { - return name.charAt(index); - } - if (index == name.length()) { - return suffix; - } - return 0; - } - - private int getNumberOfUtfBytes(int b) { - if ((b & 0x80) == 0) { - return 1; - } - int numberOfUtfBytes = 0; - while ((b & 0x80) != 0) { - b <<= 1; - numberOfUtfBytes++; - } - return numberOfUtfBytes; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (obj.getClass() == AsciiBytes.class) { - AsciiBytes other = (AsciiBytes) obj; - if (this.length == other.length) { - for (int i = 0; i < this.length; i++) { - if (this.bytes[this.offset + i] != other.bytes[other.offset + i]) { - return false; - } - } - return true; - } - } - return false; - } - - @Override - public int hashCode() { - int hash = this.hash; - if (hash == 0 && this.bytes.length > 0) { - for (int i = this.offset; i < this.offset + this.length; i++) { - int b = this.bytes[i]; - int remainingUtfBytes = getNumberOfUtfBytes(b) - 1; - b &= INITIAL_BYTE_BITMASK[remainingUtfBytes]; - for (int j = 0; j < remainingUtfBytes; j++) { - b = (b << 6) + (this.bytes[++i] & SUBSEQUENT_BYTE_BITMASK); - } - if (b <= 0xFFFF) { - hash = 31 * hash + b; - } - else { - hash = 31 * hash + ((b >> 0xA) + 0xD7C0); - hash = 31 * hash + ((b & 0x3FF) + 0xDC00); - } - } - this.hash = hash; - } - return hash; - } - - @Override - public String toString() { - if (this.string == null) { - if (this.length == 0) { - this.string = EMPTY_STRING; - } - else { - this.string = new String(this.bytes, this.offset, this.length, StandardCharsets.UTF_8); - } - } - return this.string; - } - - static String toString(byte[] bytes) { - return new String(bytes, StandardCharsets.UTF_8); - } - - static int hashCode(CharSequence charSequence) { - // We're compatible with String's hashCode() - if (charSequence instanceof StringSequence) { - // ... but save making an unnecessary String for StringSequence - return charSequence.hashCode(); - } - return charSequence.toString().hashCode(); - } - - static int hashCode(int hash, char suffix) { - return (suffix != 0) ? (31 * hash + suffix) : hash; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Bytes.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Bytes.java deleted file mode 100644 index 3ad488cbe75..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Bytes.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -/** - * Utilities for dealing with bytes from ZIP files. - * - * @author Phillip Webb - */ -final class Bytes { - - private Bytes() { - } - - static long littleEndianValue(byte[] bytes, int offset, int length) { - long value = 0; - for (int i = length - 1; i >= 0; i--) { - value = ((value << 8) | (bytes[offset + i] & 0xFF)); - } - return value; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java deleted file mode 100644 index e1a92d2de76..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; - -import org.springframework.boot.loader.data.RandomAccessData; - -/** - * A ZIP File "End of central directory record" (EOCD). - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Camille Vienot - * @see Zip File Format - */ -class CentralDirectoryEndRecord { - - private static final int MINIMUM_SIZE = 22; - - private static final int MAXIMUM_COMMENT_LENGTH = 0xFFFF; - - private static final int MAXIMUM_SIZE = MINIMUM_SIZE + MAXIMUM_COMMENT_LENGTH; - - private static final int SIGNATURE = 0x06054b50; - - private static final int COMMENT_LENGTH_OFFSET = 20; - - private static final int READ_BLOCK_SIZE = 256; - - private final Zip64End zip64End; - - private byte[] block; - - private int offset; - - private int size; - - /** - * Create a new {@link CentralDirectoryEndRecord} instance from the specified - * {@link RandomAccessData}, searching backwards from the end until a valid block is - * located. - * @param data the source data - * @throws IOException in case of I/O errors - */ - CentralDirectoryEndRecord(RandomAccessData data) throws IOException { - this.block = createBlockFromEndOfData(data, READ_BLOCK_SIZE); - this.size = MINIMUM_SIZE; - this.offset = this.block.length - this.size; - while (!isValid()) { - this.size++; - if (this.size > this.block.length) { - if (this.size >= MAXIMUM_SIZE || this.size > data.getSize()) { - throw new IOException( - "Unable to find ZIP central directory records after reading " + this.size + " bytes"); - } - this.block = createBlockFromEndOfData(data, this.size + READ_BLOCK_SIZE); - } - this.offset = this.block.length - this.size; - } - long startOfCentralDirectoryEndRecord = data.getSize() - this.size; - Zip64Locator zip64Locator = Zip64Locator.find(data, startOfCentralDirectoryEndRecord); - this.zip64End = (zip64Locator != null) ? new Zip64End(data, zip64Locator) : null; - } - - private byte[] createBlockFromEndOfData(RandomAccessData data, int size) throws IOException { - int length = (int) Math.min(data.getSize(), size); - return data.read(data.getSize() - length, length); - } - - private boolean isValid() { - if (this.block.length < MINIMUM_SIZE || Bytes.littleEndianValue(this.block, this.offset + 0, 4) != SIGNATURE) { - return false; - } - // Total size must be the structure size + comment - long commentLength = Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2); - return this.size == MINIMUM_SIZE + commentLength; - } - - /** - * Returns the location in the data that the archive actually starts. For most files - * the archive data will start at 0, however, it is possible to have prefixed bytes - * (often used for startup scripts) at the beginning of the data. - * @param data the source data - * @return the offset within the data where the archive begins - */ - long getStartOfArchive(RandomAccessData data) { - long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); - long specifiedOffset = (this.zip64End != null) ? this.zip64End.centralDirectoryOffset - : Bytes.littleEndianValue(this.block, this.offset + 16, 4); - long zip64EndSize = (this.zip64End != null) ? this.zip64End.getSize() : 0L; - int zip64LocSize = (this.zip64End != null) ? Zip64Locator.ZIP64_LOCSIZE : 0; - long actualOffset = data.getSize() - this.size - length - zip64EndSize - zip64LocSize; - return actualOffset - specifiedOffset; - } - - /** - * Return the bytes of the "Central directory" based on the offset indicated in this - * record. - * @param data the source data - * @return the central directory data - */ - RandomAccessData getCentralDirectory(RandomAccessData data) { - if (this.zip64End != null) { - return this.zip64End.getCentralDirectory(data); - } - long offset = Bytes.littleEndianValue(this.block, this.offset + 16, 4); - long length = Bytes.littleEndianValue(this.block, this.offset + 12, 4); - return data.getSubsection(offset, length); - } - - /** - * Return the number of ZIP entries in the file. - * @return the number of records in the zip - */ - int getNumberOfRecords() { - if (this.zip64End != null) { - return this.zip64End.getNumberOfRecords(); - } - long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2); - return (int) numberOfRecords; - } - - String getComment() { - int commentLength = (int) Bytes.littleEndianValue(this.block, this.offset + COMMENT_LENGTH_OFFSET, 2); - AsciiBytes comment = new AsciiBytes(this.block, this.offset + COMMENT_LENGTH_OFFSET + 2, commentLength); - return comment.toString(); - } - - boolean isZip64() { - return this.zip64End != null; - } - - /** - * A Zip64 end of central directory record. - * - * @see Chapter - * 4.3.14 of Zip64 specification - */ - private static final class Zip64End { - - private static final int ZIP64_ENDTOT = 32; // total number of entries - - private static final int ZIP64_ENDSIZ = 40; // central directory size in bytes - - private static final int ZIP64_ENDOFF = 48; // offset of first CEN header - - private final Zip64Locator locator; - - private final long centralDirectoryOffset; - - private final long centralDirectoryLength; - - private final int numberOfRecords; - - private Zip64End(RandomAccessData data, Zip64Locator locator) throws IOException { - this.locator = locator; - byte[] block = data.read(locator.getZip64EndOffset(), 56); - this.centralDirectoryOffset = Bytes.littleEndianValue(block, ZIP64_ENDOFF, 8); - this.centralDirectoryLength = Bytes.littleEndianValue(block, ZIP64_ENDSIZ, 8); - this.numberOfRecords = (int) Bytes.littleEndianValue(block, ZIP64_ENDTOT, 8); - } - - /** - * Return the size of this zip 64 end of central directory record. - * @return size of this zip 64 end of central directory record - */ - private long getSize() { - return this.locator.getZip64EndSize(); - } - - /** - * Return the bytes of the "Central directory" based on the offset indicated in - * this record. - * @param data the source data - * @return the central directory data - */ - private RandomAccessData getCentralDirectory(RandomAccessData data) { - return data.getSubsection(this.centralDirectoryOffset, this.centralDirectoryLength); - } - - /** - * Return the number of entries in the zip64 archive. - * @return the number of records in the zip - */ - private int getNumberOfRecords() { - return this.numberOfRecords; - } - - } - - /** - * A Zip64 end of central directory locator. - * - * @see Chapter - * 4.3.15 of Zip64 specification - */ - private static final class Zip64Locator { - - static final int SIGNATURE = 0x07064b50; - - static final int ZIP64_LOCSIZE = 20; // locator size - - static final int ZIP64_LOCOFF = 8; // offset of zip64 end - - private final long zip64EndOffset; - - private final long offset; - - private Zip64Locator(long offset, byte[] block) { - this.offset = offset; - this.zip64EndOffset = Bytes.littleEndianValue(block, ZIP64_LOCOFF, 8); - } - - /** - * Return the size of the zip 64 end record located by this zip64 end locator. - * @return size of the zip 64 end record located by this zip64 end locator - */ - private long getZip64EndSize() { - return this.offset - this.zip64EndOffset; - } - - /** - * Return the offset to locate {@link Zip64End}. - * @return offset of the Zip64 end of central directory record - */ - private long getZip64EndOffset() { - return this.zip64EndOffset; - } - - private static Zip64Locator find(RandomAccessData data, long centralDirectoryEndOffset) throws IOException { - long offset = centralDirectoryEndOffset - ZIP64_LOCSIZE; - if (offset >= 0) { - byte[] block = data.read(offset, ZIP64_LOCSIZE); - if (Bytes.littleEndianValue(block, 0, 4) == SIGNATURE) { - return new Zip64Locator(offset, block); - } - } - return null; - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java deleted file mode 100644 index 1f5b378848c..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryFileHeader.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; -import java.time.temporal.ValueRange; - -import org.springframework.boot.loader.data.RandomAccessData; - -/** - * A ZIP File "Central directory file header record" (CDFH). - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Dmytro Nosan - * @see Zip File Format - */ - -final class CentralDirectoryFileHeader implements FileHeader { - - private static final AsciiBytes SLASH = new AsciiBytes("/"); - - private static final byte[] NO_EXTRA = {}; - - private static final AsciiBytes NO_COMMENT = new AsciiBytes(""); - - private byte[] header; - - private int headerOffset; - - private AsciiBytes name; - - private byte[] extra; - - private AsciiBytes comment; - - private long localHeaderOffset; - - CentralDirectoryFileHeader() { - } - - CentralDirectoryFileHeader(byte[] header, int headerOffset, AsciiBytes name, byte[] extra, AsciiBytes comment, - long localHeaderOffset) { - this.header = header; - this.headerOffset = headerOffset; - this.name = name; - this.extra = extra; - this.comment = comment; - this.localHeaderOffset = localHeaderOffset; - } - - void load(byte[] data, int dataOffset, RandomAccessData variableData, long variableOffset, JarEntryFilter filter) - throws IOException { - // Load fixed part - this.header = data; - this.headerOffset = dataOffset; - long compressedSize = Bytes.littleEndianValue(data, dataOffset + 20, 4); - long uncompressedSize = Bytes.littleEndianValue(data, dataOffset + 24, 4); - long nameLength = Bytes.littleEndianValue(data, dataOffset + 28, 2); - long extraLength = Bytes.littleEndianValue(data, dataOffset + 30, 2); - long commentLength = Bytes.littleEndianValue(data, dataOffset + 32, 2); - long localHeaderOffset = Bytes.littleEndianValue(data, dataOffset + 42, 4); - // Load variable part - dataOffset += 46; - if (variableData != null) { - data = variableData.read(variableOffset + 46, nameLength + extraLength + commentLength); - dataOffset = 0; - } - this.name = new AsciiBytes(data, dataOffset, (int) nameLength); - if (filter != null) { - this.name = filter.apply(this.name); - } - this.extra = NO_EXTRA; - this.comment = NO_COMMENT; - if (extraLength > 0) { - this.extra = new byte[(int) extraLength]; - System.arraycopy(data, (int) (dataOffset + nameLength), this.extra, 0, this.extra.length); - } - this.localHeaderOffset = getLocalHeaderOffset(compressedSize, uncompressedSize, localHeaderOffset, this.extra); - if (commentLength > 0) { - this.comment = new AsciiBytes(data, (int) (dataOffset + nameLength + extraLength), (int) commentLength); - } - } - - private long getLocalHeaderOffset(long compressedSize, long uncompressedSize, long localHeaderOffset, byte[] extra) - throws IOException { - if (localHeaderOffset != 0xFFFFFFFFL) { - return localHeaderOffset; - } - int extraOffset = 0; - while (extraOffset < extra.length - 2) { - int id = (int) Bytes.littleEndianValue(extra, extraOffset, 2); - int length = (int) Bytes.littleEndianValue(extra, extraOffset, 2); - extraOffset += 4; - if (id == 1) { - int localHeaderExtraOffset = 0; - if (compressedSize == 0xFFFFFFFFL) { - localHeaderExtraOffset += 4; - } - if (uncompressedSize == 0xFFFFFFFFL) { - localHeaderExtraOffset += 4; - } - return Bytes.littleEndianValue(extra, extraOffset + localHeaderExtraOffset, 8); - } - extraOffset += length; - } - throw new IOException("Zip64 Extended Information Extra Field not found"); - } - - AsciiBytes getName() { - return this.name; - } - - @Override - public boolean hasName(CharSequence name, char suffix) { - return this.name.matches(name, suffix); - } - - boolean isDirectory() { - return this.name.endsWith(SLASH); - } - - @Override - public int getMethod() { - return (int) Bytes.littleEndianValue(this.header, this.headerOffset + 10, 2); - } - - long getTime() { - long datetime = Bytes.littleEndianValue(this.header, this.headerOffset + 12, 4); - return decodeMsDosFormatDateTime(datetime); - } - - /** - * Decode MS-DOS Date Time details. See - * Microsoft's documentation for more details of the format. - * @param datetime the date and time - * @return the date and time as milliseconds since the epoch - */ - private long decodeMsDosFormatDateTime(long datetime) { - int year = getChronoValue(((datetime >> 25) & 0x7f) + 1980, ChronoField.YEAR); - int month = getChronoValue((datetime >> 21) & 0x0f, ChronoField.MONTH_OF_YEAR); - int day = getChronoValue((datetime >> 16) & 0x1f, ChronoField.DAY_OF_MONTH); - int hour = getChronoValue((datetime >> 11) & 0x1f, ChronoField.HOUR_OF_DAY); - int minute = getChronoValue((datetime >> 5) & 0x3f, ChronoField.MINUTE_OF_HOUR); - int second = getChronoValue((datetime << 1) & 0x3e, ChronoField.SECOND_OF_MINUTE); - return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneId.systemDefault()) - .toInstant() - .truncatedTo(ChronoUnit.SECONDS) - .toEpochMilli(); - } - - long getCrc() { - return Bytes.littleEndianValue(this.header, this.headerOffset + 16, 4); - } - - @Override - public long getCompressedSize() { - return Bytes.littleEndianValue(this.header, this.headerOffset + 20, 4); - } - - @Override - public long getSize() { - return Bytes.littleEndianValue(this.header, this.headerOffset + 24, 4); - } - - byte[] getExtra() { - return this.extra; - } - - boolean hasExtra() { - return this.extra.length > 0; - } - - AsciiBytes getComment() { - return this.comment; - } - - @Override - public long getLocalHeaderOffset() { - return this.localHeaderOffset; - } - - @Override - public CentralDirectoryFileHeader clone() { - byte[] header = new byte[46]; - System.arraycopy(this.header, this.headerOffset, header, 0, header.length); - return new CentralDirectoryFileHeader(header, 0, this.name, header, this.comment, this.localHeaderOffset); - } - - static CentralDirectoryFileHeader fromRandomAccessData(RandomAccessData data, long offset, JarEntryFilter filter) - throws IOException { - CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); - byte[] bytes = data.read(offset, 46); - fileHeader.load(bytes, 0, data, offset, filter); - return fileHeader; - } - - private static int getChronoValue(long value, ChronoField field) { - ValueRange range = field.range(); - return Math.toIntExact(Math.min(Math.max(value, range.getMinimum()), range.getMaximum())); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java deleted file mode 100644 index 77184d4b0f9..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryParser.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.loader.data.RandomAccessData; - -/** - * Parses the central directory from a JAR file. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @see CentralDirectoryVisitor - */ -class CentralDirectoryParser { - - private static final int CENTRAL_DIRECTORY_HEADER_BASE_SIZE = 46; - - private final List visitors = new ArrayList<>(); - - T addVisitor(T visitor) { - this.visitors.add(visitor); - return visitor; - } - - /** - * Parse the source data, triggering {@link CentralDirectoryVisitor visitors}. - * @param data the source data - * @param skipPrefixBytes if prefix bytes should be skipped - * @return the actual archive data without any prefix bytes - * @throws IOException on error - */ - RandomAccessData parse(RandomAccessData data, boolean skipPrefixBytes) throws IOException { - CentralDirectoryEndRecord endRecord = new CentralDirectoryEndRecord(data); - if (skipPrefixBytes) { - data = getArchiveData(endRecord, data); - } - RandomAccessData centralDirectoryData = endRecord.getCentralDirectory(data); - visitStart(endRecord, centralDirectoryData); - parseEntries(endRecord, centralDirectoryData); - visitEnd(); - return data; - } - - private void parseEntries(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) - throws IOException { - byte[] bytes = centralDirectoryData.read(0, centralDirectoryData.getSize()); - CentralDirectoryFileHeader fileHeader = new CentralDirectoryFileHeader(); - int dataOffset = 0; - for (int i = 0; i < endRecord.getNumberOfRecords(); i++) { - fileHeader.load(bytes, dataOffset, null, 0, null); - visitFileHeader(dataOffset, fileHeader); - dataOffset += CENTRAL_DIRECTORY_HEADER_BASE_SIZE + fileHeader.getName().length() - + fileHeader.getComment().length() + fileHeader.getExtra().length; - } - } - - private RandomAccessData getArchiveData(CentralDirectoryEndRecord endRecord, RandomAccessData data) { - long offset = endRecord.getStartOfArchive(data); - if (offset == 0) { - return data; - } - return data.getSubsection(offset, data.getSize() - offset); - } - - private void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { - for (CentralDirectoryVisitor visitor : this.visitors) { - visitor.visitStart(endRecord, centralDirectoryData); - } - } - - private void visitFileHeader(long dataOffset, CentralDirectoryFileHeader fileHeader) { - for (CentralDirectoryVisitor visitor : this.visitors) { - visitor.visitFileHeader(fileHeader, dataOffset); - } - } - - private void visitEnd() { - for (CentralDirectoryVisitor visitor : this.visitors) { - visitor.visitEnd(); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryVisitor.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryVisitor.java deleted file mode 100644 index 1531117a880..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryVisitor.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import org.springframework.boot.loader.data.RandomAccessData; - -/** - * Callback visitor triggered by {@link CentralDirectoryParser}. - * - * @author Phillip Webb - */ -interface CentralDirectoryVisitor { - - void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData); - - void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset); - - void visitEnd(); - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/FileHeader.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/FileHeader.java deleted file mode 100644 index d2841a9dd7a..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/FileHeader.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.util.zip.ZipEntry; - -/** - * A file header record that has been loaded from a Jar file. - * - * @author Phillip Webb - * @see JarEntry - * @see CentralDirectoryFileHeader - */ -interface FileHeader { - - /** - * Returns {@code true} if the header has the given name. - * @param name the name to test - * @param suffix an additional suffix (or {@code 0}) - * @return {@code true} if the header has the given name - */ - boolean hasName(CharSequence name, char suffix); - - /** - * Return the offset of the load file header within the archive data. - * @return the local header offset - */ - long getLocalHeaderOffset(); - - /** - * Return the compressed size of the entry. - * @return the compressed size. - */ - long getCompressedSize(); - - /** - * Return the uncompressed size of the entry. - * @return the uncompressed size. - */ - long getSize(); - - /** - * Return the method used to compress the data. - * @return the zip compression method - * @see ZipEntry#STORED - * @see ZipEntry#DEFLATED - */ - int getMethod(); - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Handler.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Handler.java deleted file mode 100644 index 06dce44fe64..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/Handler.java +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.IOException; -import java.lang.ref.SoftReference; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.regex.Pattern; - -/** - * {@link URLStreamHandler} for Spring Boot loader {@link JarFile}s. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @since 1.0.0 - * @see JarFile#registerUrlProtocolHandler() - */ -public class Handler extends URLStreamHandler { - - // NOTE: in order to be found as a URL protocol handler, this class must be public, - // must be named Handler and must be in a package ending '.jar' - - private static final String JAR_PROTOCOL = "jar:"; - - private static final String FILE_PROTOCOL = "file:"; - - private static final String TOMCAT_WARFILE_PROTOCOL = "war:file:"; - - private static final String SEPARATOR = "!/"; - - private static final Pattern SEPARATOR_PATTERN = Pattern.compile(SEPARATOR, Pattern.LITERAL); - - private static final String CURRENT_DIR = "/./"; - - private static final Pattern CURRENT_DIR_PATTERN = Pattern.compile(CURRENT_DIR, Pattern.LITERAL); - - private static final String PARENT_DIR = "/../"; - - private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; - - private static final String[] FALLBACK_HANDLERS = { "sun.net.www.protocol.jar.Handler" }; - - private static URL jarContextUrl; - - private static SoftReference> rootFileCache; - - static { - rootFileCache = new SoftReference<>(null); - } - - private final JarFile jarFile; - - private URLStreamHandler fallbackHandler; - - public Handler() { - this(null); - } - - public Handler(JarFile jarFile) { - this.jarFile = jarFile; - } - - @Override - protected URLConnection openConnection(URL url) throws IOException { - if (this.jarFile != null && isUrlInJarFile(url, this.jarFile)) { - return JarURLConnection.get(url, this.jarFile); - } - try { - return JarURLConnection.get(url, getRootJarFileFromUrl(url)); - } - catch (Exception ex) { - return openFallbackConnection(url, ex); - } - } - - private boolean isUrlInJarFile(URL url, JarFile jarFile) throws MalformedURLException { - // Try the path first to save building a new url string each time - return url.getPath().startsWith(jarFile.getUrl().getPath()) - && url.toString().startsWith(jarFile.getUrlString()); - } - - private URLConnection openFallbackConnection(URL url, Exception reason) throws IOException { - try { - URLConnection connection = openFallbackTomcatConnection(url); - connection = (connection != null) ? connection : openFallbackContextConnection(url); - return (connection != null) ? connection : openFallbackHandlerConnection(url); - } - catch (Exception ex) { - if (reason instanceof IOException ioException) { - log(false, "Unable to open fallback handler", ex); - throw ioException; - } - log(true, "Unable to open fallback handler", ex); - if (reason instanceof RuntimeException runtimeException) { - throw runtimeException; - } - throw new IllegalStateException(reason); - } - } - - /** - * Attempt to open a Tomcat formatted 'jar:war:file:...' URL. This method allows us to - * use our own nested JAR support to open the content rather than the logic in - * {@code sun.net.www.protocol.jar.URLJarFile} which will extract the nested jar to - * the temp folder to that its content can be accessed. - * @param url the URL to open - * @return a {@link URLConnection} or {@code null} - */ - private URLConnection openFallbackTomcatConnection(URL url) { - String file = url.getFile(); - if (isTomcatWarUrl(file)) { - file = file.substring(TOMCAT_WARFILE_PROTOCOL.length()); - file = file.replaceFirst("\\*/", "!/"); - try { - URLConnection connection = openConnection(new URL("jar:file:" + file)); - connection.getInputStream().close(); - return connection; - } - catch (IOException ex) { - // Ignore - } - } - return null; - } - - private boolean isTomcatWarUrl(String file) { - if (file.startsWith(TOMCAT_WARFILE_PROTOCOL) || !file.contains("*/")) { - try { - URLConnection connection = new URL(file).openConnection(); - if (connection.getClass().getName().startsWith("org.apache.catalina.")) { - return true; - } - } - catch (Exception ex) { - // Ignore - } - } - return false; - } - - /** - * Attempt to open a fallback connection by using a context URL captured before the - * jar handler was replaced with our own version. Since this method doesn't use - * reflection it won't trigger "illegal reflective access operation has occurred" - * warnings on Java 13+. - * @param url the URL to open - * @return a {@link URLConnection} or {@code null} - */ - private URLConnection openFallbackContextConnection(URL url) { - try { - if (jarContextUrl != null) { - return new URL(jarContextUrl, url.toExternalForm()).openConnection(); - } - } - catch (Exception ex) { - // Ignore - } - return null; - } - - /** - * Attempt to open a fallback connection by using reflection to access Java's default - * jar {@link URLStreamHandler}. - * @param url the URL to open - * @return the {@link URLConnection} - * @throws Exception if not connection could be opened - */ - private URLConnection openFallbackHandlerConnection(URL url) throws Exception { - URLStreamHandler fallbackHandler = getFallbackHandler(); - return new URL(null, url.toExternalForm(), fallbackHandler).openConnection(); - } - - private URLStreamHandler getFallbackHandler() { - if (this.fallbackHandler != null) { - return this.fallbackHandler; - } - for (String handlerClassName : FALLBACK_HANDLERS) { - try { - Class handlerClass = Class.forName(handlerClassName); - this.fallbackHandler = (URLStreamHandler) handlerClass.getDeclaredConstructor().newInstance(); - return this.fallbackHandler; - } - catch (Exception ex) { - // Ignore - } - } - throw new IllegalStateException("Unable to find fallback handler"); - } - - private void log(boolean warning, String message, Exception cause) { - try { - Level level = warning ? Level.WARNING : Level.FINEST; - Logger.getLogger(getClass().getName()).log(level, message, cause); - } - catch (Exception ex) { - if (warning) { - System.err.println("WARNING: " + message); - } - } - } - - @Override - protected void parseURL(URL context, String spec, int start, int limit) { - if (spec.regionMatches(true, 0, JAR_PROTOCOL, 0, JAR_PROTOCOL.length())) { - setFile(context, getFileFromSpec(spec.substring(start, limit))); - } - else { - setFile(context, getFileFromContext(context, spec.substring(start, limit))); - } - } - - private String getFileFromSpec(String spec) { - int separatorIndex = spec.lastIndexOf("!/"); - if (separatorIndex == -1) { - throw new IllegalArgumentException("No !/ in spec '" + spec + "'"); - } - try { - new URL(spec.substring(0, separatorIndex)); - return spec; - } - catch (MalformedURLException ex) { - throw new IllegalArgumentException("Invalid spec URL '" + spec + "'", ex); - } - } - - private String getFileFromContext(URL context, String spec) { - String file = context.getFile(); - if (spec.startsWith("/")) { - return trimToJarRoot(file) + SEPARATOR + spec.substring(1); - } - if (file.endsWith("/")) { - return file + spec; - } - int lastSlashIndex = file.lastIndexOf('/'); - if (lastSlashIndex == -1) { - throw new IllegalArgumentException("No / found in context URL's file '" + file + "'"); - } - return file.substring(0, lastSlashIndex + 1) + spec; - } - - private String trimToJarRoot(String file) { - int lastSeparatorIndex = file.lastIndexOf(SEPARATOR); - if (lastSeparatorIndex == -1) { - throw new IllegalArgumentException("No !/ found in context URL's file '" + file + "'"); - } - return file.substring(0, lastSeparatorIndex); - } - - private void setFile(URL context, String file) { - String path = normalize(file); - String query = null; - int queryIndex = path.lastIndexOf('?'); - if (queryIndex != -1) { - query = path.substring(queryIndex + 1); - path = path.substring(0, queryIndex); - } - setURL(context, JAR_PROTOCOL, null, -1, null, null, path, query, context.getRef()); - } - - private String normalize(String file) { - if (!file.contains(CURRENT_DIR) && !file.contains(PARENT_DIR)) { - return file; - } - int afterLastSeparatorIndex = file.lastIndexOf(SEPARATOR) + SEPARATOR.length(); - String afterSeparator = file.substring(afterLastSeparatorIndex); - afterSeparator = replaceParentDir(afterSeparator); - afterSeparator = replaceCurrentDir(afterSeparator); - return file.substring(0, afterLastSeparatorIndex) + afterSeparator; - } - - private String replaceParentDir(String file) { - int parentDirIndex; - while ((parentDirIndex = file.indexOf(PARENT_DIR)) >= 0) { - int precedingSlashIndex = file.lastIndexOf('/', parentDirIndex - 1); - if (precedingSlashIndex >= 0) { - file = file.substring(0, precedingSlashIndex) + file.substring(parentDirIndex + 3); - } - else { - file = file.substring(parentDirIndex + 4); - } - } - return file; - } - - private String replaceCurrentDir(String file) { - return CURRENT_DIR_PATTERN.matcher(file).replaceAll("/"); - } - - @Override - protected int hashCode(URL u) { - return hashCode(u.getProtocol(), u.getFile()); - } - - private int hashCode(String protocol, String file) { - int result = (protocol != null) ? protocol.hashCode() : 0; - int separatorIndex = file.indexOf(SEPARATOR); - if (separatorIndex == -1) { - return result + file.hashCode(); - } - String source = file.substring(0, separatorIndex); - String entry = canonicalize(file.substring(separatorIndex + 2)); - try { - result += new URL(source).hashCode(); - } - catch (MalformedURLException ex) { - result += source.hashCode(); - } - result += entry.hashCode(); - return result; - } - - @Override - protected boolean sameFile(URL u1, URL u2) { - if (!u1.getProtocol().equals("jar") || !u2.getProtocol().equals("jar")) { - return false; - } - int separator1 = u1.getFile().indexOf(SEPARATOR); - int separator2 = u2.getFile().indexOf(SEPARATOR); - if (separator1 == -1 || separator2 == -1) { - return super.sameFile(u1, u2); - } - String nested1 = u1.getFile().substring(separator1 + SEPARATOR.length()); - String nested2 = u2.getFile().substring(separator2 + SEPARATOR.length()); - if (!nested1.equals(nested2)) { - String canonical1 = canonicalize(nested1); - String canonical2 = canonicalize(nested2); - if (!canonical1.equals(canonical2)) { - return false; - } - } - String root1 = u1.getFile().substring(0, separator1); - String root2 = u2.getFile().substring(0, separator2); - try { - return super.sameFile(new URL(root1), new URL(root2)); - } - catch (MalformedURLException ex) { - // Continue - } - return super.sameFile(u1, u2); - } - - private String canonicalize(String path) { - return SEPARATOR_PATTERN.matcher(path).replaceAll("/"); - } - - public JarFile getRootJarFileFromUrl(URL url) throws IOException { - String spec = url.getFile(); - int separatorIndex = spec.indexOf(SEPARATOR); - if (separatorIndex == -1) { - throw new MalformedURLException("Jar URL does not contain !/ separator"); - } - String name = spec.substring(0, separatorIndex); - return getRootJarFile(name); - } - - private JarFile getRootJarFile(String name) throws IOException { - try { - if (!name.startsWith(FILE_PROTOCOL)) { - throw new IllegalStateException("Not a file URL"); - } - File file = new File(URI.create(name)); - Map cache = rootFileCache.get(); - JarFile result = (cache != null) ? cache.get(file) : null; - if (result == null) { - result = new JarFile(file); - addToRootFileCache(file, result); - } - return result; - } - catch (Exception ex) { - throw new IOException("Unable to open root Jar file '" + name + "'", ex); - } - } - - /** - * Add the given {@link JarFile} to the root file cache. - * @param sourceFile the source file to add - * @param jarFile the jar file. - */ - static void addToRootFileCache(File sourceFile, JarFile jarFile) { - Map cache = rootFileCache.get(); - if (cache == null) { - cache = new ConcurrentHashMap<>(); - rootFileCache = new SoftReference<>(cache); - } - cache.put(sourceFile, jarFile); - } - - /** - * If possible, capture a URL that is configured with the original jar handler so that - * we can use it as a fallback context later. We can only do this if we know that we - * can reset the handlers after. - */ - static void captureJarContextUrl() { - if (canResetCachedUrlHandlers()) { - String handlers = System.getProperty(PROTOCOL_HANDLER); - try { - System.clearProperty(PROTOCOL_HANDLER); - try { - resetCachedUrlHandlers(); - jarContextUrl = new URL("jar:file:context.jar!/"); - URLConnection connection = jarContextUrl.openConnection(); - if (connection instanceof JarURLConnection) { - jarContextUrl = null; - } - } - catch (Exception ex) { - // Ignore - } - } - finally { - if (handlers == null) { - System.clearProperty(PROTOCOL_HANDLER); - } - else { - System.setProperty(PROTOCOL_HANDLER, handlers); - } - } - resetCachedUrlHandlers(); - } - } - - private static boolean canResetCachedUrlHandlers() { - try { - resetCachedUrlHandlers(); - return true; - } - catch (Error ex) { - return false; - } - } - - private static void resetCachedUrlHandlers() { - URL.setURLStreamHandlerFactory(null); - } - - /** - * Set if a generic static exception can be thrown when a URL cannot be connected. - * This optimization is used during class loading to save creating lots of exceptions - * which are then swallowed. - * @param useFastConnectionExceptions if fast connection exceptions can be used. - */ - public static void setUseFastConnectionExceptions(boolean useFastConnectionExceptions) { - JarURLConnection.setUseFastExceptions(useFastConnectionExceptions); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java deleted file mode 100644 index e0129a6dc27..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntriesStream.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.Closeable; -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.zip.Inflater; -import java.util.zip.ZipEntry; - -/** - * Helper class to iterate entries in a jar file and check that content matches a related - * entry. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class JarEntriesStream implements Closeable { - - private static final int BUFFER_SIZE = 4 * 1024; - - private final JarInputStream in; - - private final byte[] inBuffer = new byte[BUFFER_SIZE]; - - private final byte[] compareBuffer = new byte[BUFFER_SIZE]; - - private final Inflater inflater = new Inflater(true); - - private JarEntry entry; - - JarEntriesStream(InputStream in) throws IOException { - this.in = new JarInputStream(in); - } - - JarEntry getNextEntry() throws IOException { - this.entry = this.in.getNextJarEntry(); - if (this.entry != null) { - this.entry.getSize(); - } - this.inflater.reset(); - return this.entry; - } - - boolean matches(boolean directory, int size, int compressionMethod, InputStreamSupplier streamSupplier) - throws IOException { - if (this.entry.isDirectory() != directory) { - fail("directory"); - } - if (this.entry.getMethod() != compressionMethod) { - fail("compression method"); - } - if (this.entry.isDirectory()) { - this.in.closeEntry(); - return true; - } - try (DataInputStream expected = new DataInputStream(getInputStream(size, streamSupplier))) { - assertSameContent(expected); - } - return true; - } - - private InputStream getInputStream(int size, InputStreamSupplier streamSupplier) throws IOException { - InputStream inputStream = streamSupplier.get(); - return (this.entry.getMethod() != ZipEntry.DEFLATED) ? inputStream - : new ZipInflaterInputStream(inputStream, this.inflater, size); - } - - private void assertSameContent(DataInputStream expected) throws IOException { - int len; - while ((len = this.in.read(this.inBuffer)) > 0) { - try { - expected.readFully(this.compareBuffer, 0, len); - if (Arrays.equals(this.inBuffer, 0, len, this.compareBuffer, 0, len)) { - continue; - } - } - catch (EOFException ex) { - // Continue and throw exception due to mismatched content length. - } - fail("content"); - } - if (expected.read() != -1) { - fail("content"); - } - } - - private void fail(String check) { - throw new IllegalStateException("Content mismatch when reading security info for entry '%s' (%s check)" - .formatted(this.entry.getName(), check)); - } - - @Override - public void close() throws IOException { - this.inflater.end(); - this.in.close(); - } - - @FunctionalInterface - interface InputStreamSupplier { - - InputStream get() throws IOException; - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntry.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntry.java deleted file mode 100644 index 050818eb4ce..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntry.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.CodeSigner; -import java.security.cert.Certificate; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -/** - * Extended variant of {@link java.util.jar.JarEntry} returned by {@link JarFile}s. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class JarEntry extends java.util.jar.JarEntry implements FileHeader { - - private final int index; - - private final AsciiBytes name; - - private final AsciiBytes headerName; - - private final JarFile jarFile; - - private final long localHeaderOffset; - - private volatile JarEntryCertification certification; - - JarEntry(JarFile jarFile, int index, CentralDirectoryFileHeader header, AsciiBytes nameAlias) { - super((nameAlias != null) ? nameAlias.toString() : header.getName().toString()); - this.index = index; - this.name = (nameAlias != null) ? nameAlias : header.getName(); - this.headerName = header.getName(); - this.jarFile = jarFile; - this.localHeaderOffset = header.getLocalHeaderOffset(); - setCompressedSize(header.getCompressedSize()); - setMethod(header.getMethod()); - setCrc(header.getCrc()); - setComment(header.getComment().toString()); - setSize(header.getSize()); - setTime(header.getTime()); - if (header.hasExtra()) { - setExtra(header.getExtra()); - } - } - - int getIndex() { - return this.index; - } - - AsciiBytes getAsciiBytesName() { - return this.name; - } - - @Override - public boolean hasName(CharSequence name, char suffix) { - return this.headerName.matches(name, suffix); - } - - /** - * Return a {@link URL} for this {@link JarEntry}. - * @return the URL for the entry - * @throws MalformedURLException if the URL is not valid - */ - URL getUrl() throws MalformedURLException { - return new URL(this.jarFile.getUrl(), getName()); - } - - @Override - public Attributes getAttributes() throws IOException { - Manifest manifest = this.jarFile.getManifest(); - return (manifest != null) ? manifest.getAttributes(getName()) : null; - } - - @Override - public Certificate[] getCertificates() { - return getCertification().getCertificates(); - } - - @Override - public CodeSigner[] getCodeSigners() { - return getCertification().getCodeSigners(); - } - - private JarEntryCertification getCertification() { - if (!this.jarFile.isSigned()) { - return JarEntryCertification.NONE; - } - JarEntryCertification certification = this.certification; - if (certification == null) { - certification = this.jarFile.getCertification(this); - this.certification = certification; - } - return certification; - } - - @Override - public long getLocalHeaderOffset() { - return this.localHeaderOffset; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryCertification.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryCertification.java deleted file mode 100644 index fb2f7231415..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryCertification.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.security.CodeSigner; -import java.security.cert.Certificate; - -/** - * {@link Certificate} and {@link CodeSigner} details for a {@link JarEntry} from a signed - * {@link JarFile}. - * - * @author Phillip Webb - */ -class JarEntryCertification { - - static final JarEntryCertification NONE = new JarEntryCertification(null, null); - - private final Certificate[] certificates; - - private final CodeSigner[] codeSigners; - - JarEntryCertification(Certificate[] certificates, CodeSigner[] codeSigners) { - this.certificates = certificates; - this.codeSigners = codeSigners; - } - - Certificate[] getCertificates() { - return (this.certificates != null) ? this.certificates.clone() : null; - } - - CodeSigner[] getCodeSigners() { - return (this.codeSigners != null) ? this.codeSigners.clone() : null; - } - - static JarEntryCertification from(java.util.jar.JarEntry certifiedEntry) { - Certificate[] certificates = (certifiedEntry != null) ? certifiedEntry.getCertificates() : null; - CodeSigner[] codeSigners = (certifiedEntry != null) ? certifiedEntry.getCodeSigners() : null; - if (certificates == null && codeSigners == null) { - return NONE; - } - return new JarEntryCertification(certificates, codeSigners); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryFilter.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryFilter.java deleted file mode 100644 index 41323215273..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarEntryFilter.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -/** - * Interface that can be used to filter and optionally rename jar entries. - * - * @author Phillip Webb - */ -interface JarEntryFilter { - - /** - * Apply the jar entry filter. - * @param name the current entry name. This may be different that the original entry - * name if a previous filter has been applied - * @return the new name of the entry or {@code null} if the entry should not be - * included. - */ - AsciiBytes apply(AsciiBytes name); - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFile.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFile.java deleted file mode 100644 index 6895fed03e5..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFile.java +++ /dev/null @@ -1,476 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.FilePermission; -import java.io.IOException; -import java.io.InputStream; -import java.lang.ref.SoftReference; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; -import java.security.Permission; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.Spliterator; -import java.util.Spliterators; -import java.util.function.Supplier; -import java.util.jar.Manifest; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import java.util.zip.ZipEntry; - -import org.springframework.boot.loader.data.RandomAccessData; -import org.springframework.boot.loader.data.RandomAccessDataFile; - -/** - * Extended variant of {@link java.util.jar.JarFile} that behaves in the same way but - * offers the following additional functionality. - *
    - *
  • A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} based - * on any directory entry.
  • - *
  • A nested {@link JarFile} can be {@link #getNestedJarFile(ZipEntry) obtained} for - * embedded JAR files (as long as their entry is not compressed).
  • - *
- * - * @author Phillip Webb - * @author Andy Wilkinson - * @since 1.0.0 - */ -public class JarFile extends AbstractJarFile implements Iterable { - - private static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; - - private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; - - private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader"; - - private static final AsciiBytes META_INF = new AsciiBytes("META-INF/"); - - private static final AsciiBytes SIGNATURE_FILE_EXTENSION = new AsciiBytes(".SF"); - - private static final String READ_ACTION = "read"; - - private final RandomAccessDataFile rootFile; - - private final String pathFromRoot; - - private final RandomAccessData data; - - private final JarFileType type; - - private URL url; - - private String urlString; - - private final JarFileEntries entries; - - private final Supplier manifestSupplier; - - private SoftReference manifest; - - private boolean signed; - - private String comment; - - private volatile boolean closed; - - private volatile JarFileWrapper wrapper; - - /** - * Create a new {@link JarFile} backed by the specified file. - * @param file the root jar file - * @throws IOException if the file cannot be read - */ - public JarFile(File file) throws IOException { - this(new RandomAccessDataFile(file)); - } - - /** - * Create a new {@link JarFile} backed by the specified file. - * @param file the root jar file - * @throws IOException if the file cannot be read - */ - JarFile(RandomAccessDataFile file) throws IOException { - this(file, "", file, JarFileType.DIRECT); - } - - /** - * Private constructor used to create a new {@link JarFile} either directly or from a - * nested entry. - * @param rootFile the root jar file - * @param pathFromRoot the name of this file - * @param data the underlying data - * @param type the type of the jar file - * @throws IOException if the file cannot be read - */ - private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarFileType type) - throws IOException { - this(rootFile, pathFromRoot, data, null, type, null); - } - - private JarFile(RandomAccessDataFile rootFile, String pathFromRoot, RandomAccessData data, JarEntryFilter filter, - JarFileType type, Supplier manifestSupplier) throws IOException { - super(rootFile.getFile()); - super.close(); - this.rootFile = rootFile; - this.pathFromRoot = pathFromRoot; - CentralDirectoryParser parser = new CentralDirectoryParser(); - this.entries = parser.addVisitor(new JarFileEntries(this, filter)); - this.type = type; - parser.addVisitor(centralDirectoryVisitor()); - try { - this.data = parser.parse(data, filter == null); - } - catch (RuntimeException ex) { - try { - this.rootFile.close(); - super.close(); - } - catch (IOException ioex) { - // Ignore - } - throw ex; - } - this.manifestSupplier = (manifestSupplier != null) ? manifestSupplier : () -> { - try (InputStream inputStream = getInputStream(MANIFEST_NAME)) { - if (inputStream == null) { - return null; - } - return new Manifest(inputStream); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - }; - } - - private CentralDirectoryVisitor centralDirectoryVisitor() { - return new CentralDirectoryVisitor() { - - @Override - public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { - JarFile.this.comment = endRecord.getComment(); - } - - @Override - public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) { - AsciiBytes name = fileHeader.getName(); - if (name.startsWith(META_INF) && name.endsWith(SIGNATURE_FILE_EXTENSION)) { - JarFile.this.signed = true; - } - } - - @Override - public void visitEnd() { - } - - }; - } - - JarFileWrapper getWrapper() throws IOException { - JarFileWrapper wrapper = this.wrapper; - if (wrapper == null) { - wrapper = new JarFileWrapper(this); - this.wrapper = wrapper; - } - return wrapper; - } - - @Override - Permission getPermission() { - return new FilePermission(this.rootFile.getFile().getPath(), READ_ACTION); - } - - protected final RandomAccessDataFile getRootJarFile() { - return this.rootFile; - } - - RandomAccessData getData() { - return this.data; - } - - @Override - public Manifest getManifest() throws IOException { - Manifest manifest = (this.manifest != null) ? this.manifest.get() : null; - if (manifest == null) { - try { - manifest = this.manifestSupplier.get(); - } - catch (RuntimeException ex) { - throw new IOException(ex); - } - this.manifest = new SoftReference<>(manifest); - } - return manifest; - } - - @Override - public Enumeration entries() { - return new JarEntryEnumeration(this.entries.iterator()); - } - - @Override - public Stream stream() { - Spliterator spliterator = Spliterators.spliterator(iterator(), size(), - Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.IMMUTABLE | Spliterator.NONNULL); - return StreamSupport.stream(spliterator, false); - } - - /** - * Return an iterator for the contained entries. - * @since 2.3.0 - * @see java.lang.Iterable#iterator() - */ - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public Iterator iterator() { - return (Iterator) this.entries.iterator(this::ensureOpen); - } - - public JarEntry getJarEntry(CharSequence name) { - return this.entries.getEntry(name); - } - - @Override - public JarEntry getJarEntry(String name) { - return (JarEntry) getEntry(name); - } - - public boolean containsEntry(String name) { - return this.entries.containsEntry(name); - } - - @Override - public ZipEntry getEntry(String name) { - ensureOpen(); - return this.entries.getEntry(name); - } - - @Override - InputStream getInputStream() throws IOException { - return this.data.getInputStream(); - } - - @Override - public synchronized InputStream getInputStream(ZipEntry entry) throws IOException { - ensureOpen(); - if (entry instanceof JarEntry jarEntry) { - return this.entries.getInputStream(jarEntry); - } - return getInputStream((entry != null) ? entry.getName() : null); - } - - InputStream getInputStream(String name) throws IOException { - return this.entries.getInputStream(name); - } - - /** - * Return a nested {@link JarFile} loaded from the specified entry. - * @param entry the zip entry - * @return a {@link JarFile} for the entry - * @throws IOException if the nested jar file cannot be read - */ - public synchronized JarFile getNestedJarFile(ZipEntry entry) throws IOException { - return getNestedJarFile((JarEntry) entry); - } - - /** - * Return a nested {@link JarFile} loaded from the specified entry. - * @param entry the zip entry - * @return a {@link JarFile} for the entry - * @throws IOException if the nested jar file cannot be read - */ - public synchronized JarFile getNestedJarFile(JarEntry entry) throws IOException { - try { - return createJarFileFromEntry(entry); - } - catch (Exception ex) { - throw new IOException("Unable to open nested jar file '" + entry.getName() + "'", ex); - } - } - - private JarFile createJarFileFromEntry(JarEntry entry) throws IOException { - if (entry.isDirectory()) { - return createJarFileFromDirectoryEntry(entry); - } - return createJarFileFromFileEntry(entry); - } - - private JarFile createJarFileFromDirectoryEntry(JarEntry entry) throws IOException { - AsciiBytes name = entry.getAsciiBytesName(); - JarEntryFilter filter = (candidate) -> { - if (candidate.startsWith(name) && !candidate.equals(name)) { - return candidate.substring(name.length()); - } - return null; - }; - return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName().substring(0, name.length() - 1), - this.data, filter, JarFileType.NESTED_DIRECTORY, this.manifestSupplier); - } - - private JarFile createJarFileFromFileEntry(JarEntry entry) throws IOException { - if (entry.getMethod() != ZipEntry.STORED) { - throw new IllegalStateException( - "Unable to open nested entry '" + entry.getName() + "'. It has been compressed and nested " - + "jar files must be stored without compression. Please check the " - + "mechanism used to create your executable jar file"); - } - RandomAccessData entryData = this.entries.getEntryData(entry.getName()); - return new JarFile(this.rootFile, this.pathFromRoot + "!/" + entry.getName(), entryData, - JarFileType.NESTED_JAR); - } - - @Override - public String getComment() { - ensureOpen(); - return this.comment; - } - - @Override - public int size() { - ensureOpen(); - return this.entries.getSize(); - } - - @Override - public void close() throws IOException { - if (this.closed) { - return; - } - super.close(); - if (this.type == JarFileType.DIRECT) { - this.rootFile.close(); - } - this.closed = true; - } - - private void ensureOpen() { - if (this.closed) { - throw new IllegalStateException("zip file closed"); - } - } - - boolean isClosed() { - return this.closed; - } - - String getUrlString() throws MalformedURLException { - if (this.urlString == null) { - this.urlString = getUrl().toString(); - } - return this.urlString; - } - - @Override - public URL getUrl() throws MalformedURLException { - if (this.url == null) { - String file = this.rootFile.getFile().toURI() + this.pathFromRoot + "!/"; - file = file.replace("file:////", "file://"); // Fix UNC paths - this.url = new URL("jar", "", -1, file, new Handler(this)); - } - return this.url; - } - - @Override - public String toString() { - return getName(); - } - - @Override - public String getName() { - return this.rootFile.getFile() + this.pathFromRoot; - } - - boolean isSigned() { - return this.signed; - } - - JarEntryCertification getCertification(JarEntry entry) { - try { - return this.entries.getCertification(entry); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - public void clearCache() { - this.entries.clearCache(); - } - - protected String getPathFromRoot() { - return this.pathFromRoot; - } - - @Override - JarFileType getType() { - return this.type; - } - - /** - * Register a {@literal 'java.protocol.handler.pkgs'} property so that a - * {@link URLStreamHandler} will be located to deal with jar URLs. - */ - public static void registerUrlProtocolHandler() { - Handler.captureJarContextUrl(); - String handlers = System.getProperty(PROTOCOL_HANDLER, ""); - System.setProperty(PROTOCOL_HANDLER, - ((handlers == null || handlers.isEmpty()) ? HANDLERS_PACKAGE : handlers + "|" + HANDLERS_PACKAGE)); - resetCachedUrlHandlers(); - } - - /** - * Reset any cached handlers just in case a jar protocol has already been used. We - * reset the handler by trying to set a null {@link URLStreamHandlerFactory} which - * should have no effect other than clearing the handlers cache. - */ - private static void resetCachedUrlHandlers() { - try { - URL.setURLStreamHandlerFactory(null); - } - catch (Error ex) { - // Ignore - } - } - - /** - * An {@link Enumeration} on {@linkplain java.util.jar.JarEntry jar entries}. - */ - private static class JarEntryEnumeration implements Enumeration { - - private final Iterator iterator; - - JarEntryEnumeration(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasMoreElements() { - return this.iterator.hasNext(); - } - - @Override - public java.util.jar.JarEntry nextElement() { - return this.iterator.next(); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java deleted file mode 100644 index 558dcfd06ca..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileEntries.java +++ /dev/null @@ -1,483 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.jar.Attributes; -import java.util.jar.Attributes.Name; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; - -import org.springframework.boot.loader.data.RandomAccessData; - -/** - * Provides access to entries from a {@link JarFile}. In order to reduce memory - * consumption entry details are stored using arrays. The {@code hashCodes} array stores - * the hash code of the entry name, the {@code centralDirectoryOffsets} provides the - * offset to the central directory record and {@code positions} provides the original - * order position of the entry. The arrays are stored in hashCode order so that a binary - * search can be used to find a name. - *

- * A typical Spring Boot application will have somewhere in the region of 10,500 entries - * which should consume about 122K. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class JarFileEntries implements CentralDirectoryVisitor, Iterable { - - private static final Runnable NO_VALIDATION = () -> { - }; - - private static final String META_INF_PREFIX = "META-INF/"; - - private static final Name MULTI_RELEASE = new Name("Multi-Release"); - - private static final int BASE_VERSION = 8; - - private static final int RUNTIME_VERSION = Runtime.version().feature(); - - private static final long LOCAL_FILE_HEADER_SIZE = 30; - - private static final char SLASH = '/'; - - private static final char NO_SUFFIX = 0; - - protected static final int ENTRY_CACHE_SIZE = 25; - - private final JarFile jarFile; - - private final JarEntryFilter filter; - - private RandomAccessData centralDirectoryData; - - private int size; - - private int[] hashCodes; - - private Offsets centralDirectoryOffsets; - - private int[] positions; - - private Boolean multiReleaseJar; - - private JarEntryCertification[] certifications; - - private final Map entriesCache = Collections - .synchronizedMap(new LinkedHashMap<>(16, 0.75f, true) { - - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() >= ENTRY_CACHE_SIZE; - } - - }); - - JarFileEntries(JarFile jarFile, JarEntryFilter filter) { - this.jarFile = jarFile; - this.filter = filter; - } - - @Override - public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { - int maxSize = endRecord.getNumberOfRecords(); - this.centralDirectoryData = centralDirectoryData; - this.hashCodes = new int[maxSize]; - this.centralDirectoryOffsets = Offsets.from(endRecord); - this.positions = new int[maxSize]; - } - - @Override - public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) { - AsciiBytes name = applyFilter(fileHeader.getName()); - if (name != null) { - add(name, dataOffset); - } - } - - private void add(AsciiBytes name, long dataOffset) { - this.hashCodes[this.size] = name.hashCode(); - this.centralDirectoryOffsets.set(this.size, dataOffset); - this.positions[this.size] = this.size; - this.size++; - } - - @Override - public void visitEnd() { - sort(0, this.size - 1); - int[] positions = this.positions; - this.positions = new int[positions.length]; - for (int i = 0; i < this.size; i++) { - this.positions[positions[i]] = i; - } - } - - int getSize() { - return this.size; - } - - private void sort(int left, int right) { - // Quick sort algorithm, uses hashCodes as the source but sorts all arrays - if (left < right) { - int pivot = this.hashCodes[left + (right - left) / 2]; - int i = left; - int j = right; - while (i <= j) { - while (this.hashCodes[i] < pivot) { - i++; - } - while (this.hashCodes[j] > pivot) { - j--; - } - if (i <= j) { - swap(i, j); - i++; - j--; - } - } - if (left < j) { - sort(left, j); - } - if (right > i) { - sort(i, right); - } - } - } - - private void swap(int i, int j) { - swap(this.hashCodes, i, j); - this.centralDirectoryOffsets.swap(i, j); - swap(this.positions, i, j); - } - - @Override - public Iterator iterator() { - return new EntryIterator(NO_VALIDATION); - } - - Iterator iterator(Runnable validator) { - return new EntryIterator(validator); - } - - boolean containsEntry(CharSequence name) { - return getEntry(name, FileHeader.class, true) != null; - } - - JarEntry getEntry(CharSequence name) { - return getEntry(name, JarEntry.class, true); - } - - InputStream getInputStream(String name) throws IOException { - FileHeader entry = getEntry(name, FileHeader.class, false); - return getInputStream(entry); - } - - InputStream getInputStream(FileHeader entry) throws IOException { - if (entry == null) { - return null; - } - InputStream inputStream = getEntryData(entry).getInputStream(); - if (entry.getMethod() == ZipEntry.DEFLATED) { - inputStream = new ZipInflaterInputStream(inputStream, (int) entry.getSize()); - } - return inputStream; - } - - RandomAccessData getEntryData(String name) throws IOException { - FileHeader entry = getEntry(name, FileHeader.class, false); - if (entry == null) { - return null; - } - return getEntryData(entry); - } - - private RandomAccessData getEntryData(FileHeader entry) throws IOException { - // aspectjrt-1.7.4.jar has a different ext bytes length in the - // local directory to the central directory. We need to re-read - // here to skip them - RandomAccessData data = this.jarFile.getData(); - byte[] localHeader = data.read(entry.getLocalHeaderOffset(), LOCAL_FILE_HEADER_SIZE); - long nameLength = Bytes.littleEndianValue(localHeader, 26, 2); - long extraLength = Bytes.littleEndianValue(localHeader, 28, 2); - return data.getSubsection(entry.getLocalHeaderOffset() + LOCAL_FILE_HEADER_SIZE + nameLength + extraLength, - entry.getCompressedSize()); - } - - private T getEntry(CharSequence name, Class type, boolean cacheEntry) { - T entry = doGetEntry(name, type, cacheEntry, null); - if (!isMetaInfEntry(name) && isMultiReleaseJar()) { - int version = RUNTIME_VERSION; - AsciiBytes nameAlias = (entry instanceof JarEntry jarEntry) ? jarEntry.getAsciiBytesName() - : new AsciiBytes(name.toString()); - while (version > BASE_VERSION) { - T versionedEntry = doGetEntry("META-INF/versions/" + version + "/" + name, type, cacheEntry, nameAlias); - if (versionedEntry != null) { - return versionedEntry; - } - version--; - } - } - return entry; - } - - private boolean isMetaInfEntry(CharSequence name) { - return name.toString().startsWith(META_INF_PREFIX); - } - - private boolean isMultiReleaseJar() { - Boolean multiRelease = this.multiReleaseJar; - if (multiRelease != null) { - return multiRelease; - } - try { - Manifest manifest = this.jarFile.getManifest(); - if (manifest == null) { - multiRelease = false; - } - else { - Attributes attributes = manifest.getMainAttributes(); - multiRelease = attributes.containsKey(MULTI_RELEASE); - } - } - catch (IOException ex) { - multiRelease = false; - } - this.multiReleaseJar = multiRelease; - return multiRelease; - } - - private T doGetEntry(CharSequence name, Class type, boolean cacheEntry, - AsciiBytes nameAlias) { - int hashCode = AsciiBytes.hashCode(name); - T entry = getEntry(hashCode, name, NO_SUFFIX, type, cacheEntry, nameAlias); - if (entry == null) { - hashCode = AsciiBytes.hashCode(hashCode, SLASH); - entry = getEntry(hashCode, name, SLASH, type, cacheEntry, nameAlias); - } - return entry; - } - - private T getEntry(int hashCode, CharSequence name, char suffix, Class type, - boolean cacheEntry, AsciiBytes nameAlias) { - int index = getFirstIndex(hashCode); - while (index >= 0 && index < this.size && this.hashCodes[index] == hashCode) { - T entry = getEntry(index, type, cacheEntry, nameAlias); - if (entry.hasName(name, suffix)) { - return entry; - } - index++; - } - return null; - } - - @SuppressWarnings("unchecked") - private T getEntry(int index, Class type, boolean cacheEntry, AsciiBytes nameAlias) { - try { - long offset = this.centralDirectoryOffsets.get(index); - FileHeader cached = this.entriesCache.get(index); - FileHeader entry = (cached != null) ? cached - : CentralDirectoryFileHeader.fromRandomAccessData(this.centralDirectoryData, offset, this.filter); - if (CentralDirectoryFileHeader.class.equals(entry.getClass()) && type.equals(JarEntry.class)) { - entry = new JarEntry(this.jarFile, index, (CentralDirectoryFileHeader) entry, nameAlias); - } - if (cacheEntry && cached != entry) { - this.entriesCache.put(index, entry); - } - return (T) entry; - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - private int getFirstIndex(int hashCode) { - int index = Arrays.binarySearch(this.hashCodes, 0, this.size, hashCode); - if (index < 0) { - return -1; - } - while (index > 0 && this.hashCodes[index - 1] == hashCode) { - index--; - } - return index; - } - - void clearCache() { - this.entriesCache.clear(); - } - - private AsciiBytes applyFilter(AsciiBytes name) { - return (this.filter != null) ? this.filter.apply(name) : name; - } - - JarEntryCertification getCertification(JarEntry entry) throws IOException { - JarEntryCertification[] certifications = this.certifications; - if (certifications == null) { - certifications = getCertifications(); - this.certifications = certifications; - } - JarEntryCertification certification = certifications[entry.getIndex()]; - return (certification != null) ? certification : JarEntryCertification.NONE; - } - - private JarEntryCertification[] getCertifications() throws IOException { - JarEntryCertification[] certifications = new JarEntryCertification[this.size]; - try (JarEntriesStream entries = new JarEntriesStream(this.jarFile.getData().getInputStream())) { - java.util.jar.JarEntry entry = entries.getNextEntry(); - while (entry != null) { - JarEntry relatedEntry = this.doGetEntry(entry.getName(), JarEntry.class, false, null); - if (relatedEntry != null && entries.matches(relatedEntry.isDirectory(), (int) relatedEntry.getSize(), - relatedEntry.getMethod(), () -> getEntryData(relatedEntry).getInputStream())) { - int index = relatedEntry.getIndex(); - if (index != -1) { - certifications[index] = JarEntryCertification.from(entry); - } - } - entry = entries.getNextEntry(); - } - } - return certifications; - } - - private static void swap(int[] array, int i, int j) { - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - - private static void swap(long[] array, int i, int j) { - long temp = array[i]; - array[i] = array[j]; - array[j] = temp; - } - - /** - * Iterator for contained entries. - */ - private final class EntryIterator implements Iterator { - - private final Runnable validator; - - private int index = 0; - - private EntryIterator(Runnable validator) { - this.validator = validator; - validator.run(); - } - - @Override - public boolean hasNext() { - this.validator.run(); - return this.index < JarFileEntries.this.size; - } - - @Override - public JarEntry next() { - this.validator.run(); - if (!hasNext()) { - throw new NoSuchElementException(); - } - int entryIndex = JarFileEntries.this.positions[this.index]; - this.index++; - return getEntry(entryIndex, JarEntry.class, false, null); - } - - } - - /** - * Interface to manage offsets to central directory records. Regular zip files are - * backed by an {@code int[]} based implementation, Zip64 files are backed by a - * {@code long[]} and will consume more memory. - */ - private interface Offsets { - - void set(int index, long value); - - long get(int index); - - void swap(int i, int j); - - static Offsets from(CentralDirectoryEndRecord endRecord) { - int size = endRecord.getNumberOfRecords(); - return endRecord.isZip64() ? new Zip64Offsets(size) : new ZipOffsets(size); - } - - } - - /** - * {@link Offsets} implementation for regular zip files. - */ - private static final class ZipOffsets implements Offsets { - - private final int[] offsets; - - private ZipOffsets(int size) { - this.offsets = new int[size]; - } - - @Override - public void swap(int i, int j) { - JarFileEntries.swap(this.offsets, i, j); - } - - @Override - public void set(int index, long value) { - this.offsets[index] = (int) value; - } - - @Override - public long get(int index) { - return this.offsets[index]; - } - - } - - /** - * {@link Offsets} implementation for zip64 files. - */ - private static final class Zip64Offsets implements Offsets { - - private final long[] offsets; - - private Zip64Offsets(int size) { - this.offsets = new long[size]; - } - - @Override - public void swap(int i, int j) { - JarFileEntries.swap(this.offsets, i, j); - } - - @Override - public void set(int index, long value) { - this.offsets[index] = value; - } - - @Override - public long get(int index) { - return this.offsets[index]; - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileWrapper.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileWrapper.java deleted file mode 100644 index 8dbfe981d5d..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarFileWrapper.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.Permission; -import java.util.Enumeration; -import java.util.jar.JarEntry; -import java.util.jar.Manifest; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; - -/** - * A wrapper used to create a copy of a {@link JarFile} so that it can be safely closed - * without closing the original. - * - * @author Phillip Webb - */ -class JarFileWrapper extends AbstractJarFile { - - private final JarFile parent; - - JarFileWrapper(JarFile parent) throws IOException { - super(parent.getRootJarFile().getFile()); - this.parent = parent; - super.close(); - } - - @Override - URL getUrl() throws MalformedURLException { - return this.parent.getUrl(); - } - - @Override - JarFileType getType() { - return this.parent.getType(); - } - - @Override - Permission getPermission() { - return this.parent.getPermission(); - } - - @Override - public Manifest getManifest() throws IOException { - return this.parent.getManifest(); - } - - @Override - public Enumeration entries() { - return this.parent.entries(); - } - - @Override - public Stream stream() { - return this.parent.stream(); - } - - @Override - public JarEntry getJarEntry(String name) { - return this.parent.getJarEntry(name); - } - - @Override - public ZipEntry getEntry(String name) { - return this.parent.getEntry(name); - } - - @Override - InputStream getInputStream() throws IOException { - return this.parent.getInputStream(); - } - - @Override - public synchronized InputStream getInputStream(ZipEntry ze) throws IOException { - return this.parent.getInputStream(ze); - } - - @Override - public String getComment() { - return this.parent.getComment(); - } - - @Override - public int size() { - return this.parent.size(); - } - - @Override - public String toString() { - return this.parent.toString(); - } - - @Override - public String getName() { - return this.parent.getName(); - } - - static JarFile unwrap(java.util.jar.JarFile jarFile) { - if (jarFile instanceof JarFile file) { - return file; - } - if (jarFile instanceof JarFileWrapper wrapper) { - return unwrap(wrapper.parent); - } - throw new IllegalStateException("Not a JarFile or Wrapper"); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java deleted file mode 100644 index f61b5864161..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLEncoder; -import java.net.URLStreamHandler; -import java.nio.charset.StandardCharsets; -import java.security.Permission; - -/** - * {@link java.net.JarURLConnection} used to support {@link JarFile#getUrl()}. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Rostyslav Dudka - */ -final class JarURLConnection extends java.net.JarURLConnection { - - private static final ThreadLocal useFastExceptions = new ThreadLocal<>(); - - private static final FileNotFoundException FILE_NOT_FOUND_EXCEPTION = new FileNotFoundException( - "Jar file or entry not found"); - - private static final IllegalStateException NOT_FOUND_CONNECTION_EXCEPTION = new IllegalStateException( - FILE_NOT_FOUND_EXCEPTION); - - private static final String SEPARATOR = "!/"; - - private static final URL EMPTY_JAR_URL; - - static { - try { - EMPTY_JAR_URL = new URL("jar:", null, 0, "file:!/", new URLStreamHandler() { - @Override - protected URLConnection openConnection(URL u) throws IOException { - // Stub URLStreamHandler to prevent the wrong JAR Handler from being - // Instantiated and cached. - return null; - } - }); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - private static final JarEntryName EMPTY_JAR_ENTRY_NAME = new JarEntryName(new StringSequence("")); - - private static final JarURLConnection NOT_FOUND_CONNECTION = JarURLConnection.notFound(); - - private final AbstractJarFile jarFile; - - private Permission permission; - - private URL jarFileUrl; - - private final JarEntryName jarEntryName; - - private java.util.jar.JarEntry jarEntry; - - private JarURLConnection(URL url, AbstractJarFile jarFile, JarEntryName jarEntryName) throws IOException { - // What we pass to super is ultimately ignored - super(EMPTY_JAR_URL); - this.url = url; - this.jarFile = jarFile; - this.jarEntryName = jarEntryName; - } - - @Override - public void connect() throws IOException { - if (this.jarFile == null) { - throw FILE_NOT_FOUND_EXCEPTION; - } - if (!this.jarEntryName.isEmpty() && this.jarEntry == null) { - this.jarEntry = this.jarFile.getJarEntry(getEntryName()); - if (this.jarEntry == null) { - throwFileNotFound(this.jarEntryName, this.jarFile); - } - } - this.connected = true; - } - - @Override - public java.util.jar.JarFile getJarFile() throws IOException { - connect(); - return this.jarFile; - } - - @Override - public URL getJarFileURL() { - if (this.jarFile == null) { - throw NOT_FOUND_CONNECTION_EXCEPTION; - } - if (this.jarFileUrl == null) { - this.jarFileUrl = buildJarFileUrl(); - } - return this.jarFileUrl; - } - - private URL buildJarFileUrl() { - try { - String spec = this.jarFile.getUrl().getFile(); - if (spec.endsWith(SEPARATOR)) { - spec = spec.substring(0, spec.length() - SEPARATOR.length()); - } - if (!spec.contains(SEPARATOR)) { - return new URL(spec); - } - return new URL("jar:" + spec); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - @Override - public java.util.jar.JarEntry getJarEntry() throws IOException { - if (this.jarEntryName == null || this.jarEntryName.isEmpty()) { - return null; - } - connect(); - return this.jarEntry; - } - - @Override - public String getEntryName() { - if (this.jarFile == null) { - throw NOT_FOUND_CONNECTION_EXCEPTION; - } - return this.jarEntryName.toString(); - } - - @Override - public InputStream getInputStream() throws IOException { - if (this.jarFile == null) { - throw FILE_NOT_FOUND_EXCEPTION; - } - if (this.jarEntryName.isEmpty() && this.jarFile.getType() == JarFile.JarFileType.DIRECT) { - throw new IOException("no entry name specified"); - } - connect(); - InputStream inputStream = (this.jarEntryName.isEmpty() ? this.jarFile.getInputStream() - : this.jarFile.getInputStream(this.jarEntry)); - if (inputStream == null) { - throwFileNotFound(this.jarEntryName, this.jarFile); - } - return inputStream; - } - - private void throwFileNotFound(Object entry, AbstractJarFile jarFile) throws FileNotFoundException { - if (Boolean.TRUE.equals(useFastExceptions.get())) { - throw FILE_NOT_FOUND_EXCEPTION; - } - throw new FileNotFoundException("JAR entry " + entry + " not found in " + jarFile.getName()); - } - - @Override - public int getContentLength() { - long length = getContentLengthLong(); - if (length > Integer.MAX_VALUE) { - return -1; - } - return (int) length; - } - - @Override - public long getContentLengthLong() { - if (this.jarFile == null) { - return -1; - } - try { - if (this.jarEntryName.isEmpty()) { - return this.jarFile.size(); - } - java.util.jar.JarEntry entry = getJarEntry(); - return (entry != null) ? (int) entry.getSize() : -1; - } - catch (IOException ex) { - return -1; - } - } - - @Override - public Object getContent() throws IOException { - connect(); - return this.jarEntryName.isEmpty() ? this.jarFile : super.getContent(); - } - - @Override - public String getContentType() { - return (this.jarEntryName != null) ? this.jarEntryName.getContentType() : null; - } - - @Override - public Permission getPermission() throws IOException { - if (this.jarFile == null) { - throw FILE_NOT_FOUND_EXCEPTION; - } - if (this.permission == null) { - this.permission = this.jarFile.getPermission(); - } - return this.permission; - } - - @Override - public long getLastModified() { - if (this.jarFile == null || this.jarEntryName.isEmpty()) { - return 0; - } - try { - java.util.jar.JarEntry entry = getJarEntry(); - return (entry != null) ? entry.getTime() : 0; - } - catch (IOException ex) { - return 0; - } - } - - static void setUseFastExceptions(boolean useFastExceptions) { - JarURLConnection.useFastExceptions.set(useFastExceptions); - } - - static JarURLConnection get(URL url, JarFile jarFile) throws IOException { - StringSequence spec = new StringSequence(url.getFile()); - int index = indexOfRootSpec(spec, jarFile.getPathFromRoot()); - if (index == -1) { - return (Boolean.TRUE.equals(useFastExceptions.get()) ? NOT_FOUND_CONNECTION - : new JarURLConnection(url, null, EMPTY_JAR_ENTRY_NAME)); - } - int separator; - while ((separator = spec.indexOf(SEPARATOR, index)) > 0) { - JarEntryName entryName = JarEntryName.get(spec.subSequence(index, separator)); - JarEntry jarEntry = jarFile.getJarEntry(entryName.toCharSequence()); - if (jarEntry == null) { - return JarURLConnection.notFound(jarFile, entryName); - } - jarFile = jarFile.getNestedJarFile(jarEntry); - index = separator + SEPARATOR.length(); - } - JarEntryName jarEntryName = JarEntryName.get(spec, index); - if (Boolean.TRUE.equals(useFastExceptions.get()) && !jarEntryName.isEmpty() - && !jarFile.containsEntry(jarEntryName.toString())) { - return NOT_FOUND_CONNECTION; - } - return new JarURLConnection(url, jarFile.getWrapper(), jarEntryName); - } - - private static int indexOfRootSpec(StringSequence file, String pathFromRoot) { - int separatorIndex = file.indexOf(SEPARATOR); - if (separatorIndex < 0 || !file.startsWith(pathFromRoot, separatorIndex)) { - return -1; - } - return separatorIndex + SEPARATOR.length() + pathFromRoot.length(); - } - - private static JarURLConnection notFound() { - try { - return notFound(null, null); - } - catch (IOException ex) { - throw new IllegalStateException(ex); - } - } - - private static JarURLConnection notFound(JarFile jarFile, JarEntryName jarEntryName) throws IOException { - if (Boolean.TRUE.equals(useFastExceptions.get())) { - return NOT_FOUND_CONNECTION; - } - return new JarURLConnection(null, jarFile, jarEntryName); - } - - /** - * A JarEntryName parsed from a URL String. - */ - static class JarEntryName { - - private final StringSequence name; - - private String contentType; - - JarEntryName(StringSequence spec) { - this.name = decode(spec); - } - - private StringSequence decode(StringSequence source) { - if (source.isEmpty() || (source.indexOf('%') < 0)) { - return source; - } - ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length()); - write(source.toString(), bos); - // AsciiBytes is what is used to store the JarEntries so make it symmetric - return new StringSequence(AsciiBytes.toString(bos.toByteArray())); - } - - private void write(String source, ByteArrayOutputStream outputStream) { - int length = source.length(); - for (int i = 0; i < length; i++) { - int c = source.charAt(i); - if (c > 127) { - String encoded = URLEncoder.encode(String.valueOf((char) c), StandardCharsets.UTF_8); - write(encoded, outputStream); - } - else { - if (c == '%') { - if ((i + 2) >= length) { - throw new IllegalArgumentException( - "Invalid encoded sequence \"" + source.substring(i) + "\""); - } - c = decodeEscapeSequence(source, i); - i += 2; - } - outputStream.write(c); - } - } - } - - private char decodeEscapeSequence(String source, int i) { - int hi = Character.digit(source.charAt(i + 1), 16); - int lo = Character.digit(source.charAt(i + 2), 16); - if (hi == -1 || lo == -1) { - throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); - } - return ((char) ((hi << 4) + lo)); - } - - CharSequence toCharSequence() { - return this.name; - } - - @Override - public String toString() { - return this.name.toString(); - } - - boolean isEmpty() { - return this.name.isEmpty(); - } - - String getContentType() { - if (this.contentType == null) { - this.contentType = deduceContentType(); - } - return this.contentType; - } - - private String deduceContentType() { - // Guess the content type, don't bother with streams as mark is not supported - String type = isEmpty() ? "x-java/jar" : null; - type = (type != null) ? type : guessContentTypeFromName(toString()); - type = (type != null) ? type : "content/unknown"; - return type; - } - - static JarEntryName get(StringSequence spec) { - return get(spec, 0); - } - - static JarEntryName get(StringSequence spec, int beginIndex) { - if (spec.length() <= beginIndex) { - return EMPTY_JAR_ENTRY_NAME; - } - return new JarEntryName(spec.subSequence(beginIndex)); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/StringSequence.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/StringSequence.java deleted file mode 100644 index 27f10932ed1..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/StringSequence.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.util.Objects; - -/** - * A {@link CharSequence} backed by a single shared {@link String}. Unlike a regular - * {@link String}, {@link #subSequence(int, int)} operations will not copy the underlying - * character array. - * - * @author Phillip Webb - */ -final class StringSequence implements CharSequence { - - private final String source; - - private final int start; - - private final int end; - - private int hash; - - StringSequence(String source) { - this(source, 0, (source != null) ? source.length() : -1); - } - - StringSequence(String source, int start, int end) { - Objects.requireNonNull(source, "Source must not be null"); - if (start < 0) { - throw new StringIndexOutOfBoundsException(start); - } - if (end > source.length()) { - throw new StringIndexOutOfBoundsException(end); - } - this.source = source; - this.start = start; - this.end = end; - } - - StringSequence subSequence(int start) { - return subSequence(start, length()); - } - - @Override - public StringSequence subSequence(int start, int end) { - int subSequenceStart = this.start + start; - int subSequenceEnd = this.start + end; - if (subSequenceStart > this.end) { - throw new StringIndexOutOfBoundsException(start); - } - if (subSequenceEnd > this.end) { - throw new StringIndexOutOfBoundsException(end); - } - if (start == 0 && subSequenceEnd == this.end) { - return this; - } - return new StringSequence(this.source, subSequenceStart, subSequenceEnd); - } - - /** - * Returns {@code true} if the sequence is empty. Public to be compatible with JDK 15. - * @return {@code true} if {@link #length()} is {@code 0}, otherwise {@code false} - */ - @Override - public boolean isEmpty() { - return length() == 0; - } - - @Override - public int length() { - return this.end - this.start; - } - - @Override - public char charAt(int index) { - return this.source.charAt(this.start + index); - } - - int indexOf(char ch) { - return this.source.indexOf(ch, this.start) - this.start; - } - - int indexOf(String str) { - return this.source.indexOf(str, this.start) - this.start; - } - - int indexOf(String str, int fromIndex) { - return this.source.indexOf(str, this.start + fromIndex) - this.start; - } - - boolean startsWith(String prefix) { - return startsWith(prefix, 0); - } - - boolean startsWith(String prefix, int offset) { - int prefixLength = prefix.length(); - int length = length(); - if (length - prefixLength - offset < 0) { - return false; - } - return this.source.startsWith(prefix, this.start + offset); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof CharSequence other)) { - return false; - } - int n = length(); - if (n != other.length()) { - return false; - } - int i = 0; - while (n-- != 0) { - if (charAt(i) != other.charAt(i)) { - return false; - } - i++; - } - return true; - } - - @Override - public int hashCode() { - int hash = this.hash; - if (hash == 0 && length() > 0) { - for (int i = this.start; i < this.end; i++) { - hash = 31 * hash + this.source.charAt(i); - } - this.hash = hash; - } - return hash; - } - - @Override - public String toString() { - return this.source.substring(this.start, this.end); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/ZipInflaterInputStream.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/ZipInflaterInputStream.java deleted file mode 100644 index 487aab314df..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/ZipInflaterInputStream.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -/** - * {@link InflaterInputStream} that supports the writing of an extra "dummy" byte (which - * is required with JDK 6) and returns accurate available() results. - * - * @author Phillip Webb - */ -class ZipInflaterInputStream extends InflaterInputStream { - - private final boolean ownsInflator; - - private int available; - - private boolean extraBytesWritten; - - ZipInflaterInputStream(InputStream inputStream, int size) { - this(inputStream, new Inflater(true), size, true); - } - - ZipInflaterInputStream(InputStream inputStream, Inflater inflater, int size) { - this(inputStream, inflater, size, false); - } - - private ZipInflaterInputStream(InputStream inputStream, Inflater inflater, int size, boolean ownsInflator) { - super(inputStream, inflater, getInflaterBufferSize(size)); - this.ownsInflator = ownsInflator; - this.available = size; - } - - @Override - public int available() throws IOException { - if (this.available < 0) { - return super.available(); - } - return this.available; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int result = super.read(b, off, len); - if (result != -1) { - this.available -= result; - } - return result; - } - - @Override - public void close() throws IOException { - super.close(); - if (this.ownsInflator) { - this.inf.end(); - } - } - - @Override - protected void fill() throws IOException { - try { - super.fill(); - } - catch (EOFException ex) { - if (this.extraBytesWritten) { - throw ex; - } - this.len = 1; - this.buf[0] = 0x0; - this.extraBytesWritten = true; - this.inf.setInput(this.buf, 0, this.len); - } - } - - private static int getInflaterBufferSize(long size) { - size += 2; // inflater likes some space - size = (size > 65536) ? 8192 : size; - size = (size <= 0) ? 4096 : size; - return (int) size; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/package-info.java deleted file mode 100644 index b5a8fad855b..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jar/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Support for loading and manipulating JAR/WAR files. - */ -package org.springframework.boot.loader.jar; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarMode.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarMode.java deleted file mode 100644 index 05767e32e93..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarMode.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jarmode; - -/** - * Interface registered in {@code spring.factories} to provides extended 'jarmode' - * support. - * - * @author Phillip Webb - * @since 2.3.0 - */ -public interface JarMode { - - /** - * Returns if this accepts and can run the given mode. - * @param mode the mode to check - * @return if this instance accepts the mode - */ - boolean accepts(String mode); - - /** - * Run the jar in the given mode. - * @param mode the mode to use - * @param args any program arguments - * @throws JarModeErrorException on an error that should print a simple error message - */ - void run(String mode, String[] args) throws JarModeErrorException; - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeErrorException.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeErrorException.java deleted file mode 100644 index e2989242981..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeErrorException.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jarmode; - -/** - * Simple {@link RuntimeException} used to fail the jar mode with a simple printed error - * message. - * - * @author Phillip Webb - * @since 3.3.7 - */ -public class JarModeErrorException extends RuntimeException { - - public JarModeErrorException(String message) { - super(message); - } - - public JarModeErrorException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeLauncher.java deleted file mode 100644 index 2d51a5bf1bf..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/JarModeLauncher.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jarmode; - -import java.util.List; - -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.ClassUtils; - -/** - * Delegate class used to launch the uber jar in a specific mode. - * - * @author Phillip Webb - * @since 2.3.0 - */ -public final class JarModeLauncher { - - static final String DISABLE_SYSTEM_EXIT = JarModeLauncher.class.getName() + ".DISABLE_SYSTEM_EXIT"; - - static final String SUPPRESSED_SYSTEM_EXIT_CODE = JarModeLauncher.class.getName() + ".SUPPRESSED_SYSTEM_EXIT_CODE"; - - private JarModeLauncher() { - } - - public static void main(String[] args) { - String mode = System.getProperty("jarmode"); - boolean disableSystemExit = Boolean.getBoolean(DISABLE_SYSTEM_EXIT); - try { - runJarMode(mode, args); - if (disableSystemExit) { - System.setProperty(SUPPRESSED_SYSTEM_EXIT_CODE, "0"); - } - } - catch (Throwable ex) { - printError(ex); - if (disableSystemExit) { - System.setProperty(SUPPRESSED_SYSTEM_EXIT_CODE, "1"); - return; - } - System.exit(1); - } - } - - private static void runJarMode(String mode, String[] args) { - List candidates = SpringFactoriesLoader.loadFactories(JarMode.class, - ClassUtils.getDefaultClassLoader()); - for (JarMode candidate : candidates) { - if (candidate.accepts(mode)) { - candidate.run(mode, args); - return; - } - } - throw new JarModeErrorException("Unsupported jarmode '" + mode + "'"); - } - - private static void printError(Throwable ex) { - if (ex instanceof JarModeErrorException) { - String message = ex.getMessage(); - System.err.println("Error: " + message); - System.err.println(); - return; - } - ex.printStackTrace(); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/TestJarMode.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/TestJarMode.java deleted file mode 100644 index 02bd04cc0b1..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/TestJarMode.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jarmode; - -import java.util.Arrays; - -/** - * {@link JarMode} for testing. - * - * @author Phillip Webb - */ -class TestJarMode implements JarMode { - - @Override - public boolean accepts(String mode) { - return "test".equals(mode); - } - - @Override - public void run(String mode, String[] args) { - System.out.println("running in " + mode + " jar mode " + Arrays.asList(args)); - if (args.length > 0 && "error".equals(args[0])) { - throw new JarModeErrorException("error message"); - } - if (args.length > 0 && "fail".equals(args[0])) { - throw new IllegalStateException("bad"); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/package-info.java deleted file mode 100644 index 5a8c440323a..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/jarmode/package-info.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Support for launching the JAR using jarmode. - * - * @see org.springframework.boot.loader.jarmode.JarModeLauncher - */ -package org.springframework.boot.loader.jarmode; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java deleted file mode 100644 index 98f56cf34f1..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/JarLauncher.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.launch; - -/** - * Repackaged {@link org.springframework.boot.loader.JarLauncher}. - * - * @author Phillip Webb - * @since 3.2.0 - */ -public final class JarLauncher { - - private JarLauncher() { - } - - public static void main(String[] args) throws Exception { - org.springframework.boot.loader.JarLauncher.main(args); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java deleted file mode 100644 index bd89dc192d2..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/PropertiesLauncher.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.launch; - -/** - * Repackaged {@link org.springframework.boot.loader.PropertiesLauncher}. - * - * @author Phillip Webb - * @since 3.2.0 - */ -public final class PropertiesLauncher { - - private PropertiesLauncher() { - } - - public static void main(String[] args) throws Exception { - org.springframework.boot.loader.PropertiesLauncher.main(args); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java deleted file mode 100644 index e4258f41b88..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/WarLauncher.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.launch; - -/** - * Repackaged {@link org.springframework.boot.loader.WarLauncher}. - * - * @author Phillip Webb - * @since 3.2.0 - */ -public final class WarLauncher { - - private WarLauncher() { - } - - public static void main(String[] args) throws Exception { - org.springframework.boot.loader.WarLauncher.main(args); - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/package-info.java deleted file mode 100644 index 8ea11ba4def..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/launch/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Repackaged launcher classes. - * - * @see org.springframework.boot.loader.launch.JarLauncher - * @see org.springframework.boot.loader.launch.WarLauncher - */ -package org.springframework.boot.loader.launch; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/package-info.java deleted file mode 100644 index d354df94e74..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * System that allows self-contained JAR/WAR archives to be launched using - * {@code java -jar}. Archives can include nested packaged dependency JARs (there is no - * need to create shade style jars) and are executed without unpacking. The only - * constraint is that nested JARs must be stored in the archive uncompressed. - * - * @see org.springframework.boot.loader.JarLauncher - * @see org.springframework.boot.loader.WarLauncher - */ -package org.springframework.boot.loader; diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java deleted file mode 100644 index e63f71a01bb..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/SystemPropertyUtils.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.util; - -import java.util.HashSet; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; - -/** - * Helper class for resolving placeholders in texts. Usually applied to file paths. - *

- * A text may contain {@code $ ...} placeholders, to be resolved as system properties: - * e.g. {@code $ user.dir}. Default values can be supplied using the ":" separator between - * key and value. - *

- * Adapted from Spring. - * - * @author Juergen Hoeller - * @author Rob Harrop - * @author Dave Syer - * @since 1.0.0 - * @see System#getProperty(String) - */ -public abstract class SystemPropertyUtils { - - /** - * Prefix for system property placeholders: "${". - */ - public static final String PLACEHOLDER_PREFIX = "${"; - - /** - * Suffix for system property placeholders: "}". - */ - public static final String PLACEHOLDER_SUFFIX = "}"; - - /** - * Value separator for system property placeholders: ":". - */ - public static final String VALUE_SEPARATOR = ":"; - - private static final String SIMPLE_PREFIX = PLACEHOLDER_PREFIX.substring(1); - - /** - * Resolve ${...} placeholders in the given text, replacing them with corresponding - * system property values. - * @param text the String to resolve - * @return the resolved String - * @throws IllegalArgumentException if there is an unresolvable placeholder - * @see #PLACEHOLDER_PREFIX - * @see #PLACEHOLDER_SUFFIX - */ - public static String resolvePlaceholders(String text) { - if (text == null) { - return text; - } - return parseStringValue(null, text, text, new HashSet<>()); - } - - /** - * Resolve ${...} placeholders in the given text, replacing them with corresponding - * system property values. - * @param properties a properties instance to use in addition to System - * @param text the String to resolve - * @return the resolved String - * @throws IllegalArgumentException if there is an unresolvable placeholder - * @see #PLACEHOLDER_PREFIX - * @see #PLACEHOLDER_SUFFIX - */ - public static String resolvePlaceholders(Properties properties, String text) { - if (text == null) { - return text; - } - return parseStringValue(properties, text, text, new HashSet<>()); - } - - private static String parseStringValue(Properties properties, String value, String current, - Set visitedPlaceholders) { - - StringBuilder buf = new StringBuilder(current); - - int startIndex = current.indexOf(PLACEHOLDER_PREFIX); - while (startIndex != -1) { - int endIndex = findPlaceholderEndIndex(buf, startIndex); - if (endIndex != -1) { - String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); - String originalPlaceholder = placeholder; - if (!visitedPlaceholders.add(originalPlaceholder)) { - throw new IllegalArgumentException( - "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); - } - // Recursive invocation, parsing placeholders contained in the - // placeholder - // key. - placeholder = parseStringValue(properties, value, placeholder, visitedPlaceholders); - // Now obtain the value for the fully resolved key... - String propVal = resolvePlaceholder(properties, value, placeholder); - if (propVal == null) { - int separatorIndex = placeholder.indexOf(VALUE_SEPARATOR); - if (separatorIndex != -1) { - String actualPlaceholder = placeholder.substring(0, separatorIndex); - String defaultValue = placeholder.substring(separatorIndex + VALUE_SEPARATOR.length()); - propVal = resolvePlaceholder(properties, value, actualPlaceholder); - if (propVal == null) { - propVal = defaultValue; - } - } - } - if (propVal != null) { - // Recursive invocation, parsing placeholders contained in the - // previously resolved placeholder value. - propVal = parseStringValue(properties, value, propVal, visitedPlaceholders); - buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), propVal); - startIndex = buf.indexOf(PLACEHOLDER_PREFIX, startIndex + propVal.length()); - } - else { - // Proceed with unprocessed value. - startIndex = buf.indexOf(PLACEHOLDER_PREFIX, endIndex + PLACEHOLDER_SUFFIX.length()); - } - visitedPlaceholders.remove(originalPlaceholder); - } - else { - startIndex = -1; - } - } - - return buf.toString(); - } - - private static String resolvePlaceholder(Properties properties, String text, String placeholderName) { - String propVal = getProperty(placeholderName, null, text); - if (propVal != null) { - return propVal; - } - return (properties != null) ? properties.getProperty(placeholderName) : null; - } - - public static String getProperty(String key) { - return getProperty(key, null, ""); - } - - public static String getProperty(String key, String defaultValue) { - return getProperty(key, defaultValue, ""); - } - - /** - * Search the System properties and environment variables for a value with the - * provided key. Environment variables in {@code UPPER_CASE} style are allowed where - * System properties would normally be {@code lower.case}. - * @param key the key to resolve - * @param defaultValue the default value - * @param text optional extra context for an error message if the key resolution fails - * (e.g. if System properties are not accessible) - * @return a static property value or null of not found - */ - public static String getProperty(String key, String defaultValue, String text) { - try { - String propVal = System.getProperty(key); - if (propVal == null) { - // Fall back to searching the system environment. - propVal = System.getenv(key); - } - if (propVal == null) { - // Try with underscores. - String name = key.replace('.', '_'); - propVal = System.getenv(name); - } - if (propVal == null) { - // Try uppercase with underscores as well. - String name = key.toUpperCase(Locale.ENGLISH).replace('.', '_'); - propVal = System.getenv(name); - } - if (propVal != null) { - return propVal; - } - } - catch (Throwable ex) { - System.err.println("Could not resolve key '" + key + "' in '" + text - + "' as system property or in environment: " + ex); - } - return defaultValue; - } - - private static int findPlaceholderEndIndex(CharSequence buf, int startIndex) { - int index = startIndex + PLACEHOLDER_PREFIX.length(); - int withinNestedPlaceholder = 0; - while (index < buf.length()) { - if (substringMatch(buf, index, PLACEHOLDER_SUFFIX)) { - if (withinNestedPlaceholder > 0) { - withinNestedPlaceholder--; - index = index + PLACEHOLDER_SUFFIX.length(); - } - else { - return index; - } - } - else if (substringMatch(buf, index, SIMPLE_PREFIX)) { - withinNestedPlaceholder++; - index = index + SIMPLE_PREFIX.length(); - } - else { - index++; - } - } - return -1; - } - - private static boolean substringMatch(CharSequence str, int index, CharSequence substring) { - for (int j = 0; j < substring.length(); j++) { - int i = index + j; - if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { - return false; - } - } - return true; - } - -} diff --git a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/package-info.java b/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/package-info.java deleted file mode 100644 index 2610a45e8f0..00000000000 --- a/loader/spring-boot-loader-classic/src/main/java/org/springframework/boot/loader/util/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Utilities used by Spring Boot's JAR loading. - */ -package org.springframework.boot.loader.util; diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java deleted file mode 100644 index cb210a44e18..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/AbstractExecutableArchiveLauncherTests.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Enumeration; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; - -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.util.FileCopyUtils; - -/** - * Base class for testing {@link ExecutableArchiveLauncher} implementations. - * - * @author Andy Wilkinson - * @author Madhura Bhave - * @author Scott Frederick - */ -public abstract class AbstractExecutableArchiveLauncherTests { - - @TempDir - File tempDir; - - protected File createJarArchive(String name, String entryPrefix) throws IOException { - return createJarArchive(name, entryPrefix, false, Collections.emptyList()); - } - - @SuppressWarnings("resource") - protected File createJarArchive(String name, String entryPrefix, boolean indexed, List extraLibs) - throws IOException { - return createJarArchive(name, null, entryPrefix, indexed, extraLibs); - } - - @SuppressWarnings("resource") - protected File createJarArchive(String name, Manifest manifest, String entryPrefix, boolean indexed, - List extraLibs) throws IOException { - File archive = new File(this.tempDir, name); - JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(archive)); - if (manifest != null) { - jarOutputStream.putNextEntry(new JarEntry("META-INF/")); - jarOutputStream.putNextEntry(new JarEntry("META-INF/MANIFEST.MF")); - manifest.write(jarOutputStream); - jarOutputStream.closeEntry(); - } - jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/")); - jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classes/")); - jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/lib/")); - if (indexed) { - jarOutputStream.putNextEntry(new JarEntry(entryPrefix + "/classpath.idx")); - Writer writer = new OutputStreamWriter(jarOutputStream, StandardCharsets.UTF_8); - writer.write("- \"" + entryPrefix + "/lib/foo.jar\"\n"); - writer.write("- \"" + entryPrefix + "/lib/bar.jar\"\n"); - writer.write("- \"" + entryPrefix + "/lib/baz.jar\"\n"); - writer.flush(); - jarOutputStream.closeEntry(); - } - addNestedJars(entryPrefix, "/lib/foo.jar", jarOutputStream); - addNestedJars(entryPrefix, "/lib/bar.jar", jarOutputStream); - addNestedJars(entryPrefix, "/lib/baz.jar", jarOutputStream); - for (String lib : extraLibs) { - addNestedJars(entryPrefix, "/lib/" + lib, jarOutputStream); - } - jarOutputStream.close(); - return archive; - } - - private void addNestedJars(String entryPrefix, String lib, JarOutputStream jarOutputStream) throws IOException { - JarEntry libFoo = new JarEntry(entryPrefix + lib); - libFoo.setMethod(ZipEntry.STORED); - ByteArrayOutputStream fooJarStream = new ByteArrayOutputStream(); - new JarOutputStream(fooJarStream).close(); - libFoo.setSize(fooJarStream.size()); - CRC32 crc32 = new CRC32(); - crc32.update(fooJarStream.toByteArray()); - libFoo.setCrc(crc32.getValue()); - jarOutputStream.putNextEntry(libFoo); - jarOutputStream.write(fooJarStream.toByteArray()); - } - - protected File explode(File archive) throws IOException { - File exploded = new File(this.tempDir, "exploded"); - exploded.mkdirs(); - JarFile jarFile = new JarFile(archive); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - File entryFile = new File(exploded, entry.getName()); - if (entry.isDirectory()) { - entryFile.mkdirs(); - } - else { - FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(entryFile)); - } - } - jarFile.close(); - return exploded; - } - - protected Set getUrls(List archives) throws MalformedURLException { - Set urls = new LinkedHashSet<>(archives.size()); - for (Archive archive : archives) { - urls.add(archive.getUrl()); - } - return urls; - } - - protected final URL toUrl(File file) { - try { - return file.toURI().toURL(); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/ClassPathIndexFileTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/ClassPathIndexFileTests.java deleted file mode 100644 index ffccfde1648..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/ClassPathIndexFileTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests for {@link ClassPathIndexFile}. - * - * @author Madhura Bhave - * @author Phillip Webb - */ -class ClassPathIndexFileTests { - - @TempDir - File temp; - - @Test - void loadIfPossibleWhenRootIsNotFileReturnsNull() { - assertThatIllegalArgumentException() - .isThrownBy(() -> ClassPathIndexFile.loadIfPossible(new URL("https://example.com/file"), "test.idx")) - .withMessage("URL does not reference a file"); - } - - @Test - void loadIfPossibleWhenRootDoesNotExistReturnsNull() throws Exception { - File root = new File(this.temp, "missing"); - assertThat(ClassPathIndexFile.loadIfPossible(root.toURI().toURL(), "test.idx")).isNull(); - } - - @Test - void loadIfPossibleWhenRootIsDirectoryThrowsException() throws Exception { - File root = new File(this.temp, "directory"); - root.mkdirs(); - assertThat(ClassPathIndexFile.loadIfPossible(root.toURI().toURL(), "test.idx")).isNull(); - } - - @Test - void loadIfPossibleReturnsInstance() throws Exception { - ClassPathIndexFile indexFile = copyAndLoadTestIndexFile(); - assertThat(indexFile).isNotNull(); - } - - @Test - void sizeReturnsNumberOfLines() throws Exception { - ClassPathIndexFile indexFile = copyAndLoadTestIndexFile(); - assertThat(indexFile.size()).isEqualTo(5); - } - - @Test - void getUrlsReturnsUrls() throws Exception { - ClassPathIndexFile indexFile = copyAndLoadTestIndexFile(); - List urls = indexFile.getUrls(); - List expected = new ArrayList<>(); - expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/a.jar")); - expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/b.jar")); - expected.add(new File(this.temp, "BOOT-INF/layers/one/lib/c.jar")); - expected.add(new File(this.temp, "BOOT-INF/layers/two/lib/d.jar")); - expected.add(new File(this.temp, "BOOT-INF/layers/two/lib/e.jar")); - assertThat(urls).containsExactly(expected.stream().map(this::toUrl).toArray(URL[]::new)); - } - - private URL toUrl(File file) { - try { - return file.toURI().toURL(); - } - catch (MalformedURLException ex) { - throw new IllegalStateException(ex); - } - } - - private ClassPathIndexFile copyAndLoadTestIndexFile() throws IOException { - copyTestIndexFile(); - ClassPathIndexFile indexFile = ClassPathIndexFile.loadIfPossible(this.temp.toURI().toURL(), "test.idx"); - return indexFile; - } - - private void copyTestIndexFile() throws IOException { - Files.copy(getClass().getResourceAsStream("classpath-index-file.idx"), - new File(this.temp, "test.idx").toPath()); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/JarLauncherTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/JarLauncherTests.java deleted file mode 100644 index 3b4653b3ddc..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/JarLauncherTests.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.io.FileOutputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.jar.Attributes; -import java.util.jar.Attributes.Name; -import java.util.jar.Manifest; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.test.tools.SourceFile; -import org.springframework.core.test.tools.TestCompiler; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.function.ThrowingConsumer; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link JarLauncher}. - * - * @author Andy Wilkinson - * @author Madhura Bhave - */ -class JarLauncherTests extends AbstractExecutableArchiveLauncherTests { - - @Test - void explodedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception { - File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF")); - JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); - List archives = new ArrayList<>(); - launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot)); - for (Archive archive : archives) { - archive.close(); - } - } - - @Test - void archivedJarHasOnlyBootInfClassesAndContentsOfBootInfLibOnClasspath() throws Exception { - File jarRoot = createJarArchive("archive.jar", "BOOT-INF"); - try (JarFileArchive archive = new JarFileArchive(jarRoot)) { - JarLauncher launcher = new JarLauncher(archive); - List classPathArchives = new ArrayList<>(); - launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add); - assertThat(classPathArchives).hasSize(4); - assertThat(getUrls(classPathArchives)).containsOnly( - new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/classes!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/foo.jar!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/bar.jar!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/BOOT-INF/lib/baz.jar!/")); - for (Archive classPathArchive : classPathArchives) { - classPathArchive.close(); - } - } - } - - @Test - void explodedJarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception { - File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, Collections.emptyList())); - JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); - Iterator archives = launcher.getClassPathArchivesIterator(); - URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); - URL[] urls = classLoader.getURLs(); - assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot)); - } - - @Test - void jarFilesPresentInBootInfLibsAndNotInClasspathIndexShouldBeAddedAfterBootInfClasses() throws Exception { - ArrayList extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar")); - File explodedRoot = explode(createJarArchive("archive.jar", "BOOT-INF", true, extraLibs)); - JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); - Iterator archives = launcher.getClassPathArchivesIterator(); - URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); - URL[] urls = classLoader.getURLs(); - List expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot); - URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new); - assertThat(urls).containsExactly(expectedFileUrls); - } - - @Test - void explodedJarDefinedPackagesIncludeManifestAttributes() { - Manifest manifest = new Manifest(); - Attributes attributes = manifest.getMainAttributes(); - attributes.put(Name.MANIFEST_VERSION, "1.0"); - attributes.put(Name.IMPLEMENTATION_TITLE, "test"); - SourceFile sourceFile = SourceFile.of("explodedsample/ExampleClass.java", - new ClassPathResource("explodedsample/ExampleClass.txt")); - TestCompiler.forSystem().compile(sourceFile, ThrowingConsumer.of((compiled) -> { - File explodedRoot = explode( - createJarArchive("archive.jar", manifest, "BOOT-INF", true, Collections.emptyList())); - File target = new File(explodedRoot, "BOOT-INF/classes/explodedsample/ExampleClass.class"); - target.getParentFile().mkdirs(); - FileCopyUtils.copy(compiled.getClassLoader().getResourceAsStream("explodedsample/ExampleClass.class"), - new FileOutputStream(target)); - JarLauncher launcher = new JarLauncher(new ExplodedArchive(explodedRoot, true)); - Iterator archives = launcher.getClassPathArchivesIterator(); - URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); - Class loaded = classLoader.loadClass("explodedsample.ExampleClass"); - assertThat(loaded.getPackage().getImplementationTitle()).isEqualTo("test"); - })); - } - - protected final URL[] getExpectedFileUrls(File explodedRoot) { - return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new); - } - - protected final List getExpectedFiles(File parent) { - List expected = new ArrayList<>(); - expected.add(new File(parent, "BOOT-INF/classes")); - expected.add(new File(parent, "BOOT-INF/lib/foo.jar")); - expected.add(new File(parent, "BOOT-INF/lib/bar.jar")); - expected.add(new File(parent, "BOOT-INF/lib/baz.jar")); - return expected; - } - - protected final List getExpectedFilesWithExtraLibs(File parent) { - List expected = new ArrayList<>(); - expected.add(new File(parent, "BOOT-INF/classes")); - expected.add(new File(parent, "BOOT-INF/lib/extra-1.jar")); - expected.add(new File(parent, "BOOT-INF/lib/extra-2.jar")); - expected.add(new File(parent, "BOOT-INF/lib/foo.jar")); - expected.add(new File(parent, "BOOT-INF/lib/bar.jar")); - expected.add(new File(parent, "BOOT-INF/lib/baz.jar")); - return expected; - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java deleted file mode 100644 index f2358b570ed..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.io.InputStream; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.jar.JarFile; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link LaunchedURLClassLoader}. - * - * @author Dave Syer - * @author Phillip Webb - * @author Andy Wilkinson - */ -@SuppressWarnings("resource") -class LaunchedURLClassLoaderTests { - - @TempDir - File tempDir; - - @Test - void resolveResourceFromArchive() throws Exception { - LaunchedURLClassLoader loader = new LaunchedURLClassLoader( - new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader()); - assertThat(loader.getResource("demo/Application.java")).isNotNull(); - } - - @Test - void resolveResourcesFromArchive() throws Exception { - LaunchedURLClassLoader loader = new LaunchedURLClassLoader( - new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader()); - assertThat(loader.getResources("demo/Application.java").hasMoreElements()).isTrue(); - } - - @Test - void resolveRootPathFromArchive() throws Exception { - LaunchedURLClassLoader loader = new LaunchedURLClassLoader( - new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader()); - assertThat(loader.getResource("")).isNotNull(); - } - - @Test - void resolveRootResourcesFromArchive() throws Exception { - LaunchedURLClassLoader loader = new LaunchedURLClassLoader( - new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader()); - assertThat(loader.getResources("").hasMoreElements()).isTrue(); - } - - @Test - void resolveFromNested() throws Exception { - File file = new File(this.tempDir, "test.jar"); - TestJarCreator.createTestJar(file); - try (JarFile jarFile = new JarFile(file)) { - URL url = jarFile.getUrl(); - try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) { - URL resource = loader.getResource("nested.jar!/3.dat"); - assertThat(resource).hasToString(url + "nested.jar!/3.dat"); - try (InputStream input = resource.openConnection().getInputStream()) { - assertThat(input.read()).isEqualTo(3); - } - } - } - } - - @Test - void resolveFromNestedWhileThreadIsInterrupted() throws Exception { - File file = new File(this.tempDir, "test.jar"); - TestJarCreator.createTestJar(file); - try (JarFile jarFile = new JarFile(file)) { - URL url = jarFile.getUrl(); - try (LaunchedURLClassLoader loader = new LaunchedURLClassLoader(new URL[] { url }, null)) { - Thread.currentThread().interrupt(); - URL resource = loader.getResource("nested.jar!/3.dat"); - assertThat(resource).hasToString(url + "nested.jar!/3.dat"); - URLConnection connection = resource.openConnection(); - try (InputStream input = connection.getInputStream()) { - assertThat(input.read()).isEqualTo(3); - } - ((JarURLConnection) connection).getJarFile().close(); - } - finally { - Thread.interrupted(); - } - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java deleted file mode 100644 index 92001b84044..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/PropertiesLauncherTests.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.ref.SoftReference; -import java.net.URL; -import java.net.URLClassLoader; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.Manifest; - -import org.assertj.core.api.Condition; -import org.awaitility.Awaitility; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; -import org.springframework.boot.loader.jar.Handler; -import org.springframework.boot.loader.jar.JarFile; -import org.springframework.boot.testsupport.system.CapturedOutput; -import org.springframework.boot.testsupport.system.OutputCaptureExtension; -import org.springframework.core.io.FileSystemResource; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.hamcrest.Matchers.containsString; - -/** - * Tests for {@link PropertiesLauncher}. - * - * @author Dave Syer - * @author Andy Wilkinson - */ -@ExtendWith(OutputCaptureExtension.class) -class PropertiesLauncherTests { - - @TempDir - File tempDir; - - private PropertiesLauncher launcher; - - private ClassLoader contextClassLoader; - - private CapturedOutput output; - - @BeforeEach - void setup(CapturedOutput capturedOutput) throws Exception { - this.contextClassLoader = Thread.currentThread().getContextClassLoader(); - clearHandlerCache(); - System.setProperty("loader.home", new File("src/test/resources").getAbsolutePath()); - this.output = capturedOutput; - } - - @AfterEach - void close() throws Exception { - Thread.currentThread().setContextClassLoader(this.contextClassLoader); - System.clearProperty("loader.home"); - System.clearProperty("loader.path"); - System.clearProperty("loader.main"); - System.clearProperty("loader.config.name"); - System.clearProperty("loader.config.location"); - System.clearProperty("loader.system"); - System.clearProperty("loader.classLoader"); - clearHandlerCache(); - if (this.launcher != null) { - this.launcher.close(); - } - } - - @SuppressWarnings("unchecked") - private void clearHandlerCache() throws Exception { - Map rootFileCache = ((SoftReference>) ReflectionTestUtils - .getField(Handler.class, "rootFileCache")).get(); - if (rootFileCache != null) { - for (JarFile rootJarFile : rootFileCache.values()) { - rootJarFile.close(); - } - rootFileCache.clear(); - } - } - - @Test - void testDefaultHome() { - System.clearProperty("loader.home"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getHomeDirectory()).isEqualTo(new File(System.getProperty("user.dir"))); - } - - @Test - void testAlternateHome() throws Exception { - System.setProperty("loader.home", "src/test/resources/home"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getHomeDirectory()).isEqualTo(new File(System.getProperty("loader.home"))); - assertThat(this.launcher.getMainClass()).isEqualTo("demo.HomeApplication"); - } - - @Test - void testNonExistentHome() { - System.setProperty("loader.home", "src/test/resources/nonexistent"); - assertThatIllegalStateException().isThrownBy(PropertiesLauncher::new) - .withMessageContaining("Invalid source directory") - .withCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - void testUserSpecifiedMain() throws Exception { - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("demo.Application"); - assertThat(System.getProperty("loader.main")).isNull(); - } - - @Test - void testUserSpecifiedConfigName() throws Exception { - System.setProperty("loader.config.name", "foo"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("my.Application"); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[etc/]"); - } - - @Test - void testRootOfClasspathFirst() throws Exception { - System.setProperty("loader.config.name", "bar"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("my.BarApplication"); - } - - @Test - void testUserSpecifiedDotPath() { - System.setProperty("loader.path", "."); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[.]"); - } - - @Test - void testUserSpecifiedSlashPath() throws Exception { - System.setProperty("loader.path", "jars/"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/]"); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).areExactly(1, endingWith("app.jar")); - } - - @Test - void testUserSpecifiedWildcardPath() throws Exception { - System.setProperty("loader.path", "jars/*"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/]"); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedJarPath() throws Exception { - System.setProperty("loader.path", "jars/app.jar"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/app.jar]"); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedRootOfJarPath() throws Exception { - System.setProperty("loader.path", "jar:file:./src/test/resources/nested-jars/app.jar!/"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")) - .hasToString("[jar:file:./src/test/resources/nested-jars/app.jar!/]"); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).areExactly(1, endingWith("foo.jar!/")); - assertThat(archives).areExactly(1, endingWith("app.jar")); - } - - @Test - void testUserSpecifiedRootOfJarPathWithDot() throws Exception { - System.setProperty("loader.path", "nested-jars/app.jar!/./"); - this.launcher = new PropertiesLauncher(); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).areExactly(1, endingWith("foo.jar!/")); - assertThat(archives).areExactly(1, endingWith("app.jar")); - } - - @Test - void testUserSpecifiedRootOfJarPathWithDotAndJarPrefix() throws Exception { - System.setProperty("loader.path", "jar:file:./src/test/resources/nested-jars/app.jar!/./"); - this.launcher = new PropertiesLauncher(); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).areExactly(1, endingWith("foo.jar!/")); - } - - @Test - void testUserSpecifiedJarFileWithNestedArchives() throws Exception { - System.setProperty("loader.path", "nested-jars/app.jar"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).areExactly(1, endingWith("foo.jar!/")); - assertThat(archives).areExactly(1, endingWith("app.jar")); - } - - @Test - void testUserSpecifiedNestedJarPath() throws Exception { - System.setProperty("loader.path", "nested-jars/nested-jar-app.jar!/BOOT-INF/classes/"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")) - .hasToString("[nested-jars/nested-jar-app.jar!/BOOT-INF/classes/]"); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedDirectoryContainingJarFileWithNestedArchives() throws Exception { - System.setProperty("loader.path", "nested-jars"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedJarPathWithDot() throws Exception { - System.setProperty("loader.path", "./jars/app.jar"); - System.setProperty("loader.main", "demo.Application"); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/app.jar]"); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedClassLoader() throws Exception { - System.setProperty("loader.path", "jars/app.jar"); - System.setProperty("loader.classLoader", URLClassLoader.class.getName()); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")).hasToString("[jars/app.jar]"); - this.launcher.launch(new String[0]); - waitFor("Hello World"); - } - - @Test - void testUserSpecifiedClassPathOrder() throws Exception { - System.setProperty("loader.path", "more-jars/app.jar,jars/app.jar"); - System.setProperty("loader.classLoader", URLClassLoader.class.getName()); - this.launcher = new PropertiesLauncher(); - assertThat(ReflectionTestUtils.getField(this.launcher, "paths")) - .hasToString("[more-jars/app.jar, jars/app.jar]"); - this.launcher.launch(new String[0]); - waitFor("Hello Other World"); - } - - @Test - void testCustomClassLoaderCreation() throws Exception { - System.setProperty("loader.classLoader", TestLoader.class.getName()); - this.launcher = new PropertiesLauncher(); - ClassLoader loader = this.launcher.createClassLoader(archives()); - assertThat(loader).isNotNull(); - assertThat(loader.getClass().getName()).isEqualTo(TestLoader.class.getName()); - } - - private Iterator archives() throws Exception { - List archives = new ArrayList<>(); - String path = System.getProperty("java.class.path"); - for (String url : path.split(File.pathSeparator)) { - Archive archive = archive(url); - if (archive != null) { - archives.add(archive); - } - } - return archives.iterator(); - } - - private Archive archive(String url) throws IOException { - File file = new FileSystemResource(url).getFile(); - if (!file.exists()) { - return null; - } - if (url.endsWith(".jar")) { - return new JarFileArchive(file); - } - return new ExplodedArchive(file); - } - - @Test - void testUserSpecifiedConfigPathWins() throws Exception { - System.setProperty("loader.config.name", "foo"); - System.setProperty("loader.config.location", "classpath:bar.properties"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("my.BarApplication"); - } - - @Test - void testSystemPropertySpecifiedMain() throws Exception { - System.setProperty("loader.main", "foo.Bar"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("foo.Bar"); - } - - @Test - void testSystemPropertiesSet() { - System.setProperty("loader.system", "true"); - new PropertiesLauncher(); - assertThat(System.getProperty("loader.main")).isEqualTo("demo.Application"); - } - - @Test - void testArgsEnhanced() throws Exception { - System.setProperty("loader.args", "foo"); - this.launcher = new PropertiesLauncher(); - assertThat(Arrays.asList(this.launcher.getArgs("bar"))).hasToString("[foo, bar]"); - } - - @SuppressWarnings("unchecked") - @Test - void testLoadPathCustomizedUsingManifest() throws Exception { - System.setProperty("loader.home", this.tempDir.getAbsolutePath()); - Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().putValue("Loader-Path", "/foo.jar, /bar"); - File manifestFile = new File(this.tempDir, "META-INF/MANIFEST.MF"); - manifestFile.getParentFile().mkdirs(); - try (FileOutputStream manifestStream = new FileOutputStream(manifestFile)) { - manifest.write(manifestStream); - } - this.launcher = new PropertiesLauncher(); - assertThat((List) ReflectionTestUtils.getField(this.launcher, "paths")).containsExactly("/foo.jar", - "/bar/"); - } - - @Test - void testManifestWithPlaceholders() throws Exception { - System.setProperty("loader.home", "src/test/resources/placeholders"); - this.launcher = new PropertiesLauncher(); - assertThat(this.launcher.getMainClass()).isEqualTo("demo.FooApplication"); - } - - @Test - void encodedFileUrlLoaderPathIsHandledCorrectly() throws Exception { - File loaderPath = new File(this.tempDir, "loader path"); - loaderPath.mkdir(); - System.setProperty("loader.path", loaderPath.toURI().toURL().toString()); - this.launcher = new PropertiesLauncher(); - List archives = new ArrayList<>(); - this.launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(archives).hasSize(1); - File archiveRoot = (File) ReflectionTestUtils.getField(archives.get(0), "root"); - assertThat(archiveRoot).isEqualTo(loaderPath); - } - - @Test // gh-21575 - void loadResourceFromJarFile() throws Exception { - File jarFile = new File(this.tempDir, "app.jar"); - TestJarCreator.createTestJar(jarFile); - System.setProperty("loader.home", this.tempDir.getAbsolutePath()); - System.setProperty("loader.path", "app.jar"); - this.launcher = new PropertiesLauncher(); - try { - this.launcher.launch(new String[0]); - } - catch (Exception ex) { - // Expected ClassNotFoundException - LaunchedURLClassLoader classLoader = (LaunchedURLClassLoader) Thread.currentThread() - .getContextClassLoader(); - classLoader.close(); - } - URL resource = new URL("jar:" + jarFile.toURI() + "!/nested.jar!/3.dat"); - byte[] bytes = FileCopyUtils.copyToByteArray(resource.openStream()); - assertThat(bytes).isNotEmpty(); - } - - private void waitFor(String value) { - Awaitility.waitAtMost(Duration.ofSeconds(5)).until(this.output::toString, containsString(value)); - } - - private Condition endingWith(String value) { - return new Condition<>() { - - @Override - public boolean matches(Archive archive) { - return archive.toString().endsWith(value); - } - - }; - } - - static class TestLoader extends URLClassLoader { - - TestLoader(ClassLoader parent) { - super(new URL[0], parent); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - return super.findClass(name); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/TestJarCreator.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/TestJarCreator.java deleted file mode 100644 index 4d84f7ca795..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/TestJarCreator.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; - -/** - * Creates a simple test jar. - * - * @author Phillip Webb - */ -public abstract class TestJarCreator { - - private static final int BASE_VERSION = 8; - - private static final int RUNTIME_VERSION; - - static { - int version; - try { - Object runtimeVersion = Runtime.class.getMethod("version").invoke(null); - version = (int) runtimeVersion.getClass().getMethod("major").invoke(runtimeVersion); - } - catch (Throwable ex) { - version = BASE_VERSION; - } - RUNTIME_VERSION = version; - } - - public static void createTestJar(File file) throws Exception { - createTestJar(file, false); - } - - public static void createTestJar(File file, boolean unpackNested) throws Exception { - FileOutputStream fileOutputStream = new FileOutputStream(file); - try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) { - jarOutputStream.setComment("outer"); - writeManifest(jarOutputStream, "j1"); - writeEntry(jarOutputStream, "1.dat", 1); - writeEntry(jarOutputStream, "2.dat", 2); - writeDirEntry(jarOutputStream, "d/"); - writeEntry(jarOutputStream, "d/9.dat", 9); - writeDirEntry(jarOutputStream, "special/"); - writeEntry(jarOutputStream, "special/\u00EB.dat", '\u00EB'); - writeNestedEntry("nested.jar", unpackNested, jarOutputStream); - writeNestedEntry("another-nested.jar", unpackNested, jarOutputStream); - writeNestedEntry("space nested.jar", unpackNested, jarOutputStream); - writeNestedMultiReleaseEntry("multi-release.jar", unpackNested, jarOutputStream); - } - } - - private static void writeNestedEntry(String name, boolean unpackNested, JarOutputStream jarOutputStream) - throws Exception { - writeNestedEntry(name, unpackNested, jarOutputStream, false); - } - - private static void writeNestedMultiReleaseEntry(String name, boolean unpackNested, JarOutputStream jarOutputStream) - throws Exception { - writeNestedEntry(name, unpackNested, jarOutputStream, true); - } - - private static void writeNestedEntry(String name, boolean unpackNested, JarOutputStream jarOutputStream, - boolean multiRelease) throws Exception { - JarEntry nestedEntry = new JarEntry(name); - byte[] nestedJarData = getNestedJarData(multiRelease); - nestedEntry.setSize(nestedJarData.length); - nestedEntry.setCompressedSize(nestedJarData.length); - if (unpackNested) { - nestedEntry.setComment("UNPACK:0000000000000000000000000000000000000000"); - } - CRC32 crc32 = new CRC32(); - crc32.update(nestedJarData); - nestedEntry.setCrc(crc32.getValue()); - nestedEntry.setMethod(ZipEntry.STORED); - jarOutputStream.putNextEntry(nestedEntry); - jarOutputStream.write(nestedJarData); - jarOutputStream.closeEntry(); - } - - private static byte[] getNestedJarData(boolean multiRelease) throws Exception { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - JarOutputStream jarOutputStream = new JarOutputStream(byteArrayOutputStream); - jarOutputStream.setComment("nested"); - writeManifest(jarOutputStream, "j2", multiRelease); - if (multiRelease) { - writeEntry(jarOutputStream, "multi-release.dat", BASE_VERSION); - writeEntry(jarOutputStream, String.format("META-INF/versions/%d/multi-release.dat", RUNTIME_VERSION), - RUNTIME_VERSION); - } - else { - writeEntry(jarOutputStream, "3.dat", 3); - writeEntry(jarOutputStream, "4.dat", 4); - writeEntry(jarOutputStream, "\u00E4.dat", '\u00E4'); - } - jarOutputStream.close(); - return byteArrayOutputStream.toByteArray(); - } - - private static void writeManifest(JarOutputStream jarOutputStream, String name) throws Exception { - writeManifest(jarOutputStream, name, false); - } - - private static void writeManifest(JarOutputStream jarOutputStream, String name, boolean multiRelease) - throws Exception { - writeDirEntry(jarOutputStream, "META-INF/"); - Manifest manifest = new Manifest(); - manifest.getMainAttributes().putValue("Built-By", name); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - if (multiRelease) { - manifest.getMainAttributes().putValue("Multi-Release", Boolean.toString(true)); - } - jarOutputStream.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); - manifest.write(jarOutputStream); - jarOutputStream.closeEntry(); - } - - private static void writeDirEntry(JarOutputStream jarOutputStream, String name) throws IOException { - jarOutputStream.putNextEntry(new JarEntry(name)); - jarOutputStream.closeEntry(); - } - - private static void writeEntry(JarOutputStream jarOutputStream, String name, int data) throws IOException { - jarOutputStream.putNextEntry(new JarEntry(name)); - jarOutputStream.write(new byte[] { (byte) data }); - jarOutputStream.closeEntry(); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/WarLauncherTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/WarLauncherTests.java deleted file mode 100644 index edabafd7c3e..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/WarLauncherTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader; - -import java.io.File; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.loader.archive.ExplodedArchive; -import org.springframework.boot.loader.archive.JarFileArchive; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link WarLauncher}. - * - * @author Andy Wilkinson - * @author Scott Frederick - */ -class WarLauncherTests extends AbstractExecutableArchiveLauncherTests { - - @Test - void explodedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { - File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF")); - WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); - List archives = new ArrayList<>(); - launcher.getClassPathArchivesIterator().forEachRemaining(archives::add); - assertThat(getUrls(archives)).containsExactlyInAnyOrder(getExpectedFileUrls(explodedRoot)); - for (Archive archive : archives) { - archive.close(); - } - } - - @Test - void archivedWarHasOnlyWebInfClassesAndContentsOfWebInfLibOnClasspath() throws Exception { - File jarRoot = createJarArchive("archive.war", "WEB-INF"); - try (JarFileArchive archive = new JarFileArchive(jarRoot)) { - WarLauncher launcher = new WarLauncher(archive); - List classPathArchives = new ArrayList<>(); - launcher.getClassPathArchivesIterator().forEachRemaining(classPathArchives::add); - assertThat(getUrls(classPathArchives)).containsOnly( - new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/classes!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/foo.jar!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/bar.jar!/"), - new URL("jar:" + jarRoot.toURI().toURL() + "!/WEB-INF/lib/baz.jar!/")); - for (Archive classPathArchive : classPathArchives) { - classPathArchive.close(); - } - } - } - - @Test - void explodedWarShouldPreserveClasspathOrderWhenIndexPresent() throws Exception { - File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, Collections.emptyList())); - WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); - Iterator archives = launcher.getClassPathArchivesIterator(); - URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); - URL[] urls = classLoader.getURLs(); - assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot)); - } - - @Test - void warFilesPresentInWebInfLibsAndNotInClasspathIndexShouldBeAddedAfterWebInfClasses() throws Exception { - ArrayList extraLibs = new ArrayList<>(Arrays.asList("extra-1.jar", "extra-2.jar")); - File explodedRoot = explode(createJarArchive("archive.war", "WEB-INF", true, extraLibs)); - WarLauncher launcher = new WarLauncher(new ExplodedArchive(explodedRoot, true)); - Iterator archives = launcher.getClassPathArchivesIterator(); - URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives); - URL[] urls = classLoader.getURLs(); - List expectedFiles = getExpectedFilesWithExtraLibs(explodedRoot); - URL[] expectedFileUrls = expectedFiles.stream().map(this::toUrl).toArray(URL[]::new); - assertThat(urls).containsExactly(expectedFileUrls); - } - - protected final URL[] getExpectedFileUrls(File explodedRoot) { - return getExpectedFiles(explodedRoot).stream().map(this::toUrl).toArray(URL[]::new); - } - - protected final List getExpectedFiles(File parent) { - List expected = new ArrayList<>(); - expected.add(new File(parent, "WEB-INF/classes")); - expected.add(new File(parent, "WEB-INF/lib/foo.jar")); - expected.add(new File(parent, "WEB-INF/lib/bar.jar")); - expected.add(new File(parent, "WEB-INF/lib/baz.jar")); - return expected; - } - - protected final List getExpectedFilesWithExtraLibs(File parent) { - List expected = new ArrayList<>(); - expected.add(new File(parent, "WEB-INF/classes")); - expected.add(new File(parent, "WEB-INF/lib/extra-1.jar")); - expected.add(new File(parent, "WEB-INF/lib/extra-2.jar")); - expected.add(new File(parent, "WEB-INF/lib/foo.jar")); - expected.add(new File(parent, "WEB-INF/lib/bar.jar")); - expected.add(new File(parent, "WEB-INF/lib/baz.jar")); - return expected; - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java deleted file mode 100755 index e39b342deb9..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/ExplodedArchiveTests.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.archive; - -import java.io.File; -import java.io.FileOutputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; -import org.springframework.boot.loader.archive.Archive.Entry; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ExplodedArchive}. - * - * @author Phillip Webb - * @author Dave Syer - * @author Andy Wilkinson - */ -class ExplodedArchiveTests { - - @TempDir - File tempDir; - - private File rootDirectory; - - private ExplodedArchive archive; - - @BeforeEach - void setup() throws Exception { - createArchive(); - } - - @AfterEach - void tearDown() throws Exception { - if (this.archive != null) { - this.archive.close(); - } - } - - private void createArchive() throws Exception { - createArchive(null); - } - - private void createArchive(String directoryName) throws Exception { - File file = new File(this.tempDir, "test.jar"); - TestJarCreator.createTestJar(file); - this.rootDirectory = (StringUtils.hasText(directoryName) ? new File(this.tempDir, directoryName) - : new File(this.tempDir, UUID.randomUUID().toString())); - JarFile jarFile = new JarFile(file); - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - File destination = new File(this.rootDirectory.getAbsolutePath() + File.separator + entry.getName()); - destination.getParentFile().mkdirs(); - if (entry.isDirectory()) { - destination.mkdir(); - } - else { - FileCopyUtils.copy(jarFile.getInputStream(entry), new FileOutputStream(destination)); - } - } - this.archive = new ExplodedArchive(this.rootDirectory); - jarFile.close(); - } - - @Test - void getManifest() throws Exception { - assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1"); - } - - @Test - void getEntries() { - Map entries = getEntriesMap(this.archive); - assertThat(entries).hasSize(12); - } - - @Test - void getUrl() throws Exception { - assertThat(this.archive.getUrl()).isEqualTo(this.rootDirectory.toURI().toURL()); - } - - @Test - void getUrlWithSpaceInPath() throws Exception { - createArchive("spaces in the name"); - assertThat(this.archive.getUrl()).isEqualTo(this.rootDirectory.toURI().toURL()); - } - - @Test - void getNestedArchive() throws Exception { - Entry entry = getEntriesMap(this.archive).get("nested.jar"); - Archive nested = this.archive.getNestedArchive(entry); - assertThat(nested.getUrl()).hasToString(this.rootDirectory.toURI() + "nested.jar"); - nested.close(); - } - - @Test - void nestedDirArchive() throws Exception { - Entry entry = getEntriesMap(this.archive).get("d/"); - Archive nested = this.archive.getNestedArchive(entry); - Map nestedEntries = getEntriesMap(nested); - assertThat(nestedEntries).hasSize(1); - assertThat(nested.getUrl()).hasToString("file:" + this.rootDirectory.toURI().getPath() + "d/"); - } - - @Test - void getNonRecursiveEntriesForRoot() throws Exception { - try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("/"), false)) { - Map entries = getEntriesMap(explodedArchive); - assertThat(entries).hasSizeGreaterThan(1); - } - } - - @Test - void getNonRecursiveManifest() throws Exception { - try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"))) { - assertThat(explodedArchive.getManifest()).isNotNull(); - Map entries = getEntriesMap(explodedArchive); - assertThat(entries).hasSize(4); - } - } - - @Test - void getNonRecursiveManifestEvenIfNonRecursive() throws Exception { - try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"), false)) { - assertThat(explodedArchive.getManifest()).isNotNull(); - Map entries = getEntriesMap(explodedArchive); - assertThat(entries).hasSize(3); - } - } - - @Test - void getResourceAsStream() throws Exception { - try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"))) { - assertThat(explodedArchive.getManifest()).isNotNull(); - URLClassLoader loader = new URLClassLoader(new URL[] { explodedArchive.getUrl() }); - assertThat(loader.getResourceAsStream("META-INF/spring/application.xml")).isNotNull(); - loader.close(); - } - } - - @Test - void getResourceAsStreamNonRecursive() throws Exception { - try (ExplodedArchive explodedArchive = new ExplodedArchive(new File("src/test/resources/root"), false)) { - assertThat(explodedArchive.getManifest()).isNotNull(); - URLClassLoader loader = new URLClassLoader(new URL[] { explodedArchive.getUrl() }); - assertThat(loader.getResourceAsStream("META-INF/spring/application.xml")).isNotNull(); - loader.close(); - } - } - - private Map getEntriesMap(Archive archive) { - Map entries = new HashMap<>(); - for (Archive.Entry entry : archive) { - entries.put(entry.getName(), entry); - } - return entries; - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java deleted file mode 100755 index a78304edd83..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/archive/JarFileArchiveTests.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.archive; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarOutputStream; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; -import org.springframework.boot.loader.archive.Archive.Entry; -import org.springframework.boot.loader.jar.JarFile; -import org.springframework.util.FileCopyUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link JarFileArchive}. - * - * @author Phillip Webb - * @author Andy Wilkinson - * @author Camille Vienot - */ -class JarFileArchiveTests { - - @TempDir - File tempDir; - - private File rootJarFile; - - private JarFileArchive archive; - - private String rootJarFileUrl; - - @BeforeEach - void setup() throws Exception { - setup(false); - } - - @AfterEach - void tearDown() throws Exception { - this.archive.close(); - } - - private void setup(boolean unpackNested) throws Exception { - this.rootJarFile = new File(this.tempDir, "root.jar"); - this.rootJarFileUrl = this.rootJarFile.toURI().toString(); - TestJarCreator.createTestJar(this.rootJarFile, unpackNested); - if (this.archive != null) { - this.archive.close(); - } - this.archive = new JarFileArchive(this.rootJarFile); - } - - @Test - void getManifest() throws Exception { - assertThat(this.archive.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1"); - } - - @Test - void getEntries() { - Map entries = getEntriesMap(this.archive); - assertThat(entries).hasSize(12); - } - - @Test - void getUrl() throws Exception { - URL url = this.archive.getUrl(); - assertThat(url).hasToString(this.rootJarFileUrl); - } - - @Test - void getNestedArchive() throws Exception { - Entry entry = getEntriesMap(this.archive).get("nested.jar"); - try (Archive nested = this.archive.getNestedArchive(entry)) { - assertThat(nested.getUrl()).hasToString("jar:" + this.rootJarFileUrl + "!/nested.jar!/"); - } - } - - @Test - void getNestedUnpackedArchive() throws Exception { - setup(true); - Entry entry = getEntriesMap(this.archive).get("nested.jar"); - try (Archive nested = this.archive.getNestedArchive(entry)) { - assertThat(nested.getUrl().toString()).startsWith("file:"); - assertThat(nested.getUrl().toString()).endsWith("/nested.jar"); - } - } - - @Test - void unpackedLocationsAreUniquePerArchive() throws Exception { - setup(true); - Entry entry = getEntriesMap(this.archive).get("nested.jar"); - URL firstNestedUrl; - try (Archive firstNested = this.archive.getNestedArchive(entry)) { - firstNestedUrl = firstNested.getUrl(); - } - this.archive.close(); - setup(true); - entry = getEntriesMap(this.archive).get("nested.jar"); - try (Archive secondNested = this.archive.getNestedArchive(entry)) { - URL secondNestedUrl = secondNested.getUrl(); - assertThat(secondNestedUrl).isNotEqualTo(firstNestedUrl); - } - } - - @Test - void unpackedLocationsFromSameArchiveShareSameParent() throws Exception { - setup(true); - try (Archive nestedArchive = this.archive.getNestedArchive(getEntriesMap(this.archive).get("nested.jar")); - Archive anotherNestedArchive = this.archive - .getNestedArchive(getEntriesMap(this.archive).get("another-nested.jar"))) { - File nested = new File(nestedArchive.getUrl().toURI()); - File anotherNested = new File(anotherNestedArchive.getUrl().toURI()); - assertThat(nested).hasParent(anotherNested.getParent()); - } - } - - @Test - void filesInZip64ArchivesAreAllListed() throws IOException { - File file = new File(this.tempDir, "test.jar"); - FileCopyUtils.copy(writeZip64Jar(), file); - try (JarFileArchive zip64Archive = new JarFileArchive(file)) { - @SuppressWarnings("deprecation") - Iterator entries = zip64Archive.iterator(); - for (int i = 0; i < 65537; i++) { - assertThat(entries.hasNext()).as(i + "nth file is present").isTrue(); - entries.next(); - } - } - } - - @Test - void nestedZip64ArchivesAreHandledGracefully() throws Exception { - File file = new File(this.tempDir, "test.jar"); - try (JarOutputStream output = new JarOutputStream(new FileOutputStream(file))) { - JarEntry zip64JarEntry = new JarEntry("nested/zip64.jar"); - output.putNextEntry(zip64JarEntry); - byte[] zip64JarData = writeZip64Jar(); - zip64JarEntry.setSize(zip64JarData.length); - zip64JarEntry.setCompressedSize(zip64JarData.length); - zip64JarEntry.setMethod(ZipEntry.STORED); - CRC32 crc32 = new CRC32(); - crc32.update(zip64JarData); - zip64JarEntry.setCrc(crc32.getValue()); - output.write(zip64JarData); - output.closeEntry(); - } - try (JarFile jarFile = new JarFile(file)) { - ZipEntry nestedEntry = jarFile.getEntry("nested/zip64.jar"); - try (JarFile nestedJarFile = jarFile.getNestedJarFile(nestedEntry)) { - Iterator iterator = nestedJarFile.iterator(); - for (int i = 0; i < 65537; i++) { - assertThat(iterator.hasNext()).as(i + "nth file is present").isTrue(); - iterator.next(); - } - } - } - } - - private byte[] writeZip64Jar() throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (JarOutputStream jarOutput = new JarOutputStream(bytes)) { - for (int i = 0; i < 65537; i++) { - jarOutput.putNextEntry(new JarEntry(i + ".dat")); - jarOutput.closeEntry(); - } - } - return bytes.toByteArray(); - } - - private Map getEntriesMap(Archive archive) { - Map entries = new HashMap<>(); - for (Archive.Entry entry : archive) { - entries.put(entry.getName(), entry); - } - return entries; - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java deleted file mode 100644 index a8668c7c5b3..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/data/RandomAccessDataFileTests.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.data; - -import java.io.EOFException; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatNullPointerException; - -/** - * Tests for {@link RandomAccessDataFile}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class RandomAccessDataFileTests { - - private static final byte[] BYTES; - - static { - BYTES = new byte[256]; - for (int i = 0; i < BYTES.length; i++) { - BYTES[i] = (byte) i; - } - } - - private File tempFile; - - private RandomAccessDataFile file; - - private InputStream inputStream; - - @BeforeEach - void setup(@TempDir File tempDir) throws Exception { - this.tempFile = new File(tempDir, "tempFile"); - FileOutputStream outputStream = new FileOutputStream(this.tempFile); - outputStream.write(BYTES); - outputStream.close(); - this.file = new RandomAccessDataFile(this.tempFile); - this.inputStream = this.file.getInputStream(); - } - - @AfterEach - void cleanup() throws Exception { - this.inputStream.close(); - this.file.close(); - } - - @Test - void fileNotNull() { - assertThatIllegalArgumentException().isThrownBy(() -> new RandomAccessDataFile(null)) - .withMessageContaining("File must not be null"); - } - - @Test - void fileExists() { - File file = new File("/does/not/exist"); - assertThatIllegalArgumentException().isThrownBy(() -> new RandomAccessDataFile(file)) - .withMessageContaining(String.format("File %s must exist", file.getAbsolutePath())); - } - - @Test - void readWithOffsetAndLengthShouldRead() throws Exception { - byte[] read = this.file.read(2, 3); - assertThat(read).isEqualTo(new byte[] { 2, 3, 4 }); - } - - @Test - void readWhenOffsetIsBeyondEOFShouldThrowException() { - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.read(257, 0)); - } - - @Test - void readWhenOffsetIsBeyondEndOfSubsectionShouldThrowException() { - RandomAccessData subsection = this.file.getSubsection(0, 10); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> subsection.read(11, 0)); - } - - @Test - void readWhenOffsetPlusLengthGreaterThanEOFShouldThrowException() { - assertThatExceptionOfType(EOFException.class).isThrownBy(() -> this.file.read(256, 1)); - } - - @Test - void readWhenOffsetPlusLengthGreaterThanEndOfSubsectionShouldThrowException() { - RandomAccessData subsection = this.file.getSubsection(0, 10); - assertThatExceptionOfType(EOFException.class).isThrownBy(() -> subsection.read(10, 1)); - } - - @Test - void inputStreamRead() throws Exception { - for (int i = 0; i <= 255; i++) { - assertThat(this.inputStream.read()).isEqualTo(i); - } - } - - @Test - void inputStreamReadNullBytes() { - assertThatNullPointerException().isThrownBy(() -> this.inputStream.read(null)) - .withMessage("Bytes must not be null"); - } - - @Test - void inputStreamReadNullBytesWithOffset() { - assertThatNullPointerException().isThrownBy(() -> this.inputStream.read(null, 0, 1)) - .withMessage("Bytes must not be null"); - } - - @Test - void inputStreamReadBytes() throws Exception { - byte[] b = new byte[256]; - int amountRead = this.inputStream.read(b); - assertThat(b).isEqualTo(BYTES); - assertThat(amountRead).isEqualTo(256); - } - - @Test - void inputStreamReadOffsetBytes() throws Exception { - byte[] b = new byte[7]; - this.inputStream.skip(1); - int amountRead = this.inputStream.read(b, 2, 3); - assertThat(b).isEqualTo(new byte[] { 0, 0, 1, 2, 3, 0, 0 }); - assertThat(amountRead).isEqualTo(3); - } - - @Test - void inputStreamReadMoreBytesThanAvailable() throws Exception { - byte[] b = new byte[257]; - int amountRead = this.inputStream.read(b); - assertThat(b).startsWith(BYTES); - assertThat(amountRead).isEqualTo(256); - } - - @Test - void inputStreamReadPastEnd() throws Exception { - this.inputStream.skip(255); - assertThat(this.inputStream.read()).isEqualTo(0xFF); - assertThat(this.inputStream.read()).isEqualTo(-1); - assertThat(this.inputStream.read()).isEqualTo(-1); - } - - @Test - void inputStreamReadZeroLength() throws Exception { - byte[] b = new byte[] { 0x0F }; - int amountRead = this.inputStream.read(b, 0, 0); - assertThat(b).isEqualTo(new byte[] { 0x0F }); - assertThat(amountRead).isZero(); - assertThat(this.inputStream.read()).isZero(); - } - - @Test - void inputStreamSkip() throws Exception { - long amountSkipped = this.inputStream.skip(4); - assertThat(this.inputStream.read()).isEqualTo(4); - assertThat(amountSkipped).isEqualTo(4L); - } - - @Test - void inputStreamSkipMoreThanAvailable() throws Exception { - long amountSkipped = this.inputStream.skip(257); - assertThat(this.inputStream.read()).isEqualTo(-1); - assertThat(amountSkipped).isEqualTo(256L); - } - - @Test - void inputStreamSkipPastEnd() throws Exception { - this.inputStream.skip(256); - long amountSkipped = this.inputStream.skip(1); - assertThat(amountSkipped).isZero(); - } - - @Test - void inputStreamAvailable() throws Exception { - assertThat(this.inputStream.available()).isEqualTo(256); - this.inputStream.skip(56); - assertThat(this.inputStream.available()).isEqualTo(200); - this.inputStream.skip(200); - assertThat(this.inputStream.available()).isZero(); - } - - @Test - void subsectionNegativeOffset() { - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(-1, 1)); - } - - @Test - void subsectionNegativeLength() { - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(0, -1)); - } - - @Test - void subsectionZeroLength() throws Exception { - RandomAccessData subsection = this.file.getSubsection(0, 0); - assertThat(subsection.getInputStream().read()).isEqualTo(-1); - } - - @Test - void subsectionTooBig() { - this.file.getSubsection(0, 256); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(0, 257)); - } - - @Test - void subsectionTooBigWithOffset() { - this.file.getSubsection(1, 255); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> this.file.getSubsection(1, 256)); - } - - @Test - void subsection() throws Exception { - RandomAccessData subsection = this.file.getSubsection(1, 1); - assertThat(subsection.getInputStream().read()).isOne(); - } - - @Test - void inputStreamReadPastSubsection() throws Exception { - RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(); - assertThat(inputStream.read()).isOne(); - assertThat(inputStream.read()).isEqualTo(2); - assertThat(inputStream.read()).isEqualTo(-1); - } - - @Test - void inputStreamReadBytesPastSubsection() throws Exception { - RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(); - byte[] b = new byte[3]; - int amountRead = inputStream.read(b); - assertThat(b).isEqualTo(new byte[] { 1, 2, 0 }); - assertThat(amountRead).isEqualTo(2); - } - - @Test - void inputStreamSkipPastSubsection() throws Exception { - RandomAccessData subsection = this.file.getSubsection(1, 2); - InputStream inputStream = subsection.getInputStream(); - assertThat(inputStream.skip(3)).isEqualTo(2L); - assertThat(inputStream.read()).isEqualTo(-1); - } - - @Test - void inputStreamSkipNegative() throws Exception { - assertThat(this.inputStream.skip(-1)).isZero(); - } - - @Test - void getFile() { - assertThat(this.file.getFile()).isEqualTo(this.tempFile); - } - - @Test - void concurrentReads() throws Exception { - ExecutorService executorService = Executors.newFixedThreadPool(20); - List> results = new ArrayList<>(); - for (int i = 0; i < 100; i++) { - results.add(executorService.submit(() -> { - InputStream subsectionInputStream = RandomAccessDataFileTests.this.file.getSubsection(0, 256) - .getInputStream(); - byte[] b = new byte[256]; - subsectionInputStream.read(b); - return Arrays.equals(b, BYTES); - })); - } - for (Future future : results) { - assertThat(future.get()).isTrue(); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/AsciiBytesTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/AsciiBytesTests.java deleted file mode 100644 index d51e71823a7..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/AsciiBytesTests.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link AsciiBytes}. - * - * @author Phillip Webb - * @author Andy Wilkinson - */ -class AsciiBytesTests { - - private static final char NO_SUFFIX = 0; - - @Test - void createFromBytes() { - AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66 }); - assertThat(bytes).hasToString("AB"); - } - - @Test - void createFromBytesWithOffset() { - AsciiBytes bytes = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2); - assertThat(bytes).hasToString("BC"); - } - - @Test - void createFromString() { - AsciiBytes bytes = new AsciiBytes("AB"); - assertThat(bytes).hasToString("AB"); - } - - @Test - void length() { - AsciiBytes b1 = new AsciiBytes(new byte[] { 65, 66 }); - AsciiBytes b2 = new AsciiBytes(new byte[] { 65, 66, 67, 68 }, 1, 2); - assertThat(b1.length()).isEqualTo(2); - assertThat(b2.length()).isEqualTo(2); - } - - @Test - void startWith() { - AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 }); - AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 }); - AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2); - AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 }); - assertThat(abc.startsWith(abc)).isTrue(); - assertThat(abc.startsWith(ab)).isTrue(); - assertThat(abc.startsWith(bc)).isFalse(); - assertThat(abc.startsWith(abcd)).isFalse(); - } - - @Test - void endsWith() { - AsciiBytes abc = new AsciiBytes(new byte[] { 65, 66, 67 }); - AsciiBytes bc = new AsciiBytes(new byte[] { 65, 66, 67 }, 1, 2); - AsciiBytes ab = new AsciiBytes(new byte[] { 65, 66 }); - AsciiBytes aabc = new AsciiBytes(new byte[] { 65, 65, 66, 67 }); - assertThat(abc.endsWith(abc)).isTrue(); - assertThat(abc.endsWith(bc)).isTrue(); - assertThat(abc.endsWith(ab)).isFalse(); - assertThat(abc.endsWith(aabc)).isFalse(); - } - - @Test - void substringFromBeingIndex() { - AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 }); - assertThat(abcd.substring(0)).hasToString("ABCD"); - assertThat(abcd.substring(1)).hasToString("BCD"); - assertThat(abcd.substring(2)).hasToString("CD"); - assertThat(abcd.substring(3)).hasToString("D"); - assertThat(abcd.substring(4).toString()).isEmpty(); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> abcd.substring(5)); - } - - @Test - void substring() { - AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 }); - assertThat(abcd.substring(0, 4)).hasToString("ABCD"); - assertThat(abcd.substring(1, 3)).hasToString("BC"); - assertThat(abcd.substring(3, 4)).hasToString("D"); - assertThat(abcd.substring(3, 3).toString()).isEmpty(); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> abcd.substring(3, 5)); - } - - @Test - void hashCodeAndEquals() { - AsciiBytes abcd = new AsciiBytes(new byte[] { 65, 66, 67, 68 }); - AsciiBytes bc = new AsciiBytes(new byte[] { 66, 67 }); - AsciiBytes bc_substring = new AsciiBytes(new byte[] { 65, 66, 67, 68 }).substring(1, 3); - AsciiBytes bc_string = new AsciiBytes("BC"); - assertThat(bc).hasSameHashCodeAs(bc); - assertThat(bc).hasSameHashCodeAs(bc_substring); - assertThat(bc).hasSameHashCodeAs(bc_string); - assertThat(bc).isEqualTo(bc); - assertThat(bc).isEqualTo(bc_substring); - assertThat(bc).isEqualTo(bc_string); - assertThat(bc.hashCode()).isNotEqualTo(abcd.hashCode()); - assertThat(bc).isNotEqualTo(abcd); - } - - @Test - void hashCodeSameAsString() { - hashCodeSameAsString("abcABC123xyz!"); - } - - @Test - void hashCodeSameAsStringWithSpecial() { - hashCodeSameAsString("special/\u00EB.dat"); - } - - @Test - void hashCodeSameAsStringWithCyrillicCharacters() { - hashCodeSameAsString("\u0432\u0435\u0441\u043D\u0430"); - } - - @Test - void hashCodeSameAsStringWithEmoji() { - hashCodeSameAsString("\ud83d\udca9"); - } - - private void hashCodeSameAsString(String input) { - assertThat(new AsciiBytes(input)).hasSameHashCodeAs(input); - } - - @Test - void matchesSameAsString() { - matchesSameAsString("abcABC123xyz!"); - } - - @Test - void matchesSameAsStringWithSpecial() { - matchesSameAsString("special/\u00EB.dat"); - } - - @Test - void matchesSameAsStringWithCyrillicCharacters() { - matchesSameAsString("\u0432\u0435\u0441\u043D\u0430"); - } - - @Test - void matchesDifferentLengths() { - assertThat(new AsciiBytes("abc").matches("ab", NO_SUFFIX)).isFalse(); - assertThat(new AsciiBytes("abc").matches("abcd", NO_SUFFIX)).isFalse(); - assertThat(new AsciiBytes("abc").matches("abc", NO_SUFFIX)).isTrue(); - assertThat(new AsciiBytes("abc").matches("a", 'b')).isFalse(); - assertThat(new AsciiBytes("abc").matches("abc", 'd')).isFalse(); - assertThat(new AsciiBytes("abc").matches("ab", 'c')).isTrue(); - } - - @Test - void matchesSuffix() { - assertThat(new AsciiBytes("ab").matches("a", 'b')).isTrue(); - } - - @Test - void matchesSameAsStringWithEmoji() { - matchesSameAsString("\ud83d\udca9"); - } - - @Test - void hashCodeFromInstanceMatchesHashCodeFromString() { - String name = "fonts/宋体/simsun.ttf"; - assertThat(new AsciiBytes(name).hashCode()).isEqualTo(AsciiBytes.hashCode(name)); - } - - @Test - void instanceCreatedFromCharSequenceMatchesSameCharSequence() { - String name = "fonts/宋体/simsun.ttf"; - assertThat(new AsciiBytes(name).matches(name, NO_SUFFIX)).isTrue(); - } - - private void matchesSameAsString(String input) { - assertThat(new AsciiBytes(input).matches(input, NO_SUFFIX)).isTrue(); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java deleted file mode 100644 index 859828e91d4..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/CentralDirectoryParserTests.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; -import org.springframework.boot.loader.data.RandomAccessData; -import org.springframework.boot.loader.data.RandomAccessDataFile; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link CentralDirectoryParser}. - * - * @author Phillip Webb - */ -class CentralDirectoryParserTests { - - private File jarFile; - - private RandomAccessDataFile jarData; - - @BeforeEach - void setup(@TempDir File tempDir) throws Exception { - this.jarFile = new File(tempDir, "test.jar"); - TestJarCreator.createTestJar(this.jarFile); - this.jarData = new RandomAccessDataFile(this.jarFile); - } - - @AfterEach - void tearDown() throws IOException { - this.jarData.close(); - } - - @Test - void visitsInOrder() throws Exception { - MockCentralDirectoryVisitor visitor = new MockCentralDirectoryVisitor(); - CentralDirectoryParser parser = new CentralDirectoryParser(); - parser.addVisitor(visitor); - parser.parse(this.jarData, false); - List invocations = visitor.getInvocations(); - assertThat(invocations).startsWith("visitStart").endsWith("visitEnd").contains("visitFileHeader"); - } - - @Test - void visitRecords() throws Exception { - Collector collector = new Collector(); - CentralDirectoryParser parser = new CentralDirectoryParser(); - parser.addVisitor(collector); - parser.parse(this.jarData, false); - Iterator headers = collector.getHeaders().iterator(); - assertThat(headers.next().getName()).hasToString("META-INF/"); - assertThat(headers.next().getName()).hasToString("META-INF/MANIFEST.MF"); - assertThat(headers.next().getName()).hasToString("1.dat"); - assertThat(headers.next().getName()).hasToString("2.dat"); - assertThat(headers.next().getName()).hasToString("d/"); - assertThat(headers.next().getName()).hasToString("d/9.dat"); - assertThat(headers.next().getName()).hasToString("special/"); - assertThat(headers.next().getName()).hasToString("special/\u00EB.dat"); - assertThat(headers.next().getName()).hasToString("nested.jar"); - assertThat(headers.next().getName()).hasToString("another-nested.jar"); - assertThat(headers.next().getName()).hasToString("space nested.jar"); - assertThat(headers.next().getName()).hasToString("multi-release.jar"); - assertThat(headers.hasNext()).isFalse(); - } - - static class Collector implements CentralDirectoryVisitor { - - private final List headers = new ArrayList<>(); - - @Override - public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { - } - - @Override - public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) { - this.headers.add(fileHeader.clone()); - } - - @Override - public void visitEnd() { - } - - List getHeaders() { - return this.headers; - } - - } - - static class MockCentralDirectoryVisitor implements CentralDirectoryVisitor { - - private final List invocations = new ArrayList<>(); - - @Override - public void visitStart(CentralDirectoryEndRecord endRecord, RandomAccessData centralDirectoryData) { - this.invocations.add("visitStart"); - } - - @Override - public void visitFileHeader(CentralDirectoryFileHeader fileHeader, long dataOffset) { - this.invocations.add("visitFileHeader"); - } - - @Override - public void visitEnd() { - this.invocations.add("visitEnd"); - } - - List getInvocations() { - return this.invocations; - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java deleted file mode 100644 index 9f92ac2c70a..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/HandlerTests.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLConnection; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link Handler}. - * - * @author Andy Wilkinson - */ -@ExtendWith(JarUrlProtocolHandler.class) -class HandlerTests { - - private final Handler handler = new Handler(); - - @Test - void parseUrlWithJarRootContextAndAbsoluteSpecThatUsesContext() throws MalformedURLException { - String spec = "/entry.txt"; - URL context = createUrl("file:example.jar!/"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); - } - - @Test - void parseUrlWithDirectoryEntryContextAndAbsoluteSpecThatUsesContext() throws MalformedURLException { - String spec = "/entry.txt"; - URL context = createUrl("file:example.jar!/dir/"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); - } - - @Test - void parseUrlWithJarRootContextAndRelativeSpecThatUsesContext() throws MalformedURLException { - String spec = "entry.txt"; - URL context = createUrl("file:example.jar!/"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/entry.txt"); - } - - @Test - void parseUrlWithDirectoryEntryContextAndRelativeSpecThatUsesContext() throws MalformedURLException { - String spec = "entry.txt"; - URL context = createUrl("file:example.jar!/dir/"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/dir/entry.txt"); - } - - @Test - void parseUrlWithFileEntryContextAndRelativeSpecThatUsesContext() throws MalformedURLException { - String spec = "entry.txt"; - URL context = createUrl("file:example.jar!/dir/file"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:file:example.jar!/dir/entry.txt"); - } - - @Test - void parseUrlWithSpecThatIgnoresContext() throws MalformedURLException { - JarFile.registerUrlProtocolHandler(); - String spec = "jar:file:/other.jar!/nested!/entry.txt"; - URL context = createUrl("file:example.jar!/dir/file"); - this.handler.parseURL(context, spec, 0, spec.length()); - assertThat(context.toExternalForm()).isEqualTo("jar:jar:file:/other.jar!/nested!/entry.txt"); - } - - @Test - void sameFileReturnsFalseForUrlsWithDifferentProtocols() throws MalformedURLException { - assertThat(this.handler.sameFile(new URL("jar:file:foo.jar!/content.txt"), new URL("file:/foo.jar"))).isFalse(); - } - - @Test - void sameFileReturnsFalseForDifferentFileInSameJar() throws MalformedURLException { - assertThat(this.handler.sameFile(new URL("jar:file:foo.jar!/the/path/to/the/first/content.txt"), - new URL("jar:file:/foo.jar!/content.txt"))) - .isFalse(); - } - - @Test - void sameFileReturnsFalseForSameFileInDifferentJars() throws MalformedURLException { - assertThat(this.handler.sameFile(new URL("jar:file:/the/path/to/the/first.jar!/content.txt"), - new URL("jar:file:/second.jar!/content.txt"))) - .isFalse(); - } - - @Test - void sameFileReturnsTrueForSameFileInSameJar() throws MalformedURLException { - assertThat(this.handler.sameFile(new URL("jar:file:/the/path/to/the/first.jar!/content.txt"), - new URL("jar:file:/the/path/to/the/first.jar!/content.txt"))) - .isTrue(); - } - - @Test - void sameFileReturnsTrueForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar() - throws MalformedURLException { - assertThat(this.handler.sameFile(new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt"), - new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt"))) - .isTrue(); - } - - @Test - void hashCodesAreEqualForUrlsThatReferenceSameFileViaNestedArchiveAndFromRootOfJar() throws MalformedURLException { - assertThat(this.handler.hashCode(new URL("jar:file:/test.jar!/BOOT-INF/classes!/foo.txt"))) - .isEqualTo(this.handler.hashCode(new URL("jar:file:/test.jar!/BOOT-INF/classes/foo.txt"))); - } - - @Test - void urlWithSpecReferencingParentDirectory() throws MalformedURLException { - assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd", - "../directoryB/c/d/e.xsd"); - } - - @Test - void urlWithSpecReferencingAncestorDirectoryOutsideJarStopsAtJarRoot() throws MalformedURLException { - assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd", - "../../../../../../directoryB/b.xsd"); - } - - @Test - void urlWithSpecReferencingCurrentDirectory() throws MalformedURLException { - assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes!/xsd/directoryA/a.xsd", - "./directoryB/c/d/e.xsd"); - } - - @Test - void urlWithRef() throws MalformedURLException { - assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes", "!/foo.txt#alpha"); - } - - @Test - void urlWithQuery() throws MalformedURLException { - assertStandardAndCustomHandlerUrlsAreEqual("file:/test.jar!/BOOT-INF/classes", "!/foo.txt?alpha"); - } - - @Test - void fallbackToJdksJarUrlStreamHandler(@TempDir File tempDir) throws Exception { - File testJar = new File(tempDir, "test.jar"); - TestJarCreator.createTestJar(testJar); - URLConnection connection = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/", this.handler) - .openConnection(); - assertThat(connection).isInstanceOf(JarURLConnection.class); - ((JarURLConnection) connection).getJarFile().close(); - URLConnection jdkConnection = new URL(null, "jar:file:" + testJar.toURI().toURL() + "!/nested.jar!/", - this.handler) - .openConnection(); - assertThat(jdkConnection).isNotInstanceOf(JarURLConnection.class); - assertThat(jdkConnection.getClass().getName()).endsWith(".JarURLConnection"); - } - - @Test - void whenJarHasAPlusInItsPathConnectionJarFileMatchesOriginalJarFile(@TempDir File tempDir) throws Exception { - File testJar = new File(tempDir, "t+e+s+t.jar"); - TestJarCreator.createTestJar(testJar); - URL url = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/3.dat", this.handler); - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (JarFile jarFile = JarFileWrapper.unwrap(connection.getJarFile())) { - assertThat(jarFile.getRootJarFile().getFile()).isEqualTo(testJar); - } - } - - @Test - void whenJarHasASpaceInItsPathConnectionJarFileMatchesOriginalJarFile(@TempDir File tempDir) throws Exception { - File testJar = new File(tempDir, "t e s t.jar"); - TestJarCreator.createTestJar(testJar); - URL url = new URL(null, "jar:" + testJar.toURI().toURL() + "!/nested.jar!/3.dat", this.handler); - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (JarFile jarFile = JarFileWrapper.unwrap(connection.getJarFile())) { - assertThat(jarFile.getRootJarFile().getFile()).isEqualTo(testJar); - } - } - - private void assertStandardAndCustomHandlerUrlsAreEqual(String context, String spec) throws MalformedURLException { - URL standardUrl = new URL(new URL("jar:" + context), spec); - URL customHandlerUrl = new URL(new URL("jar", null, -1, context, this.handler), spec); - assertThat(customHandlerUrl).hasToString(standardUrl.toString()); - assertThat(customHandlerUrl.getFile()).isEqualTo(standardUrl.getFile()); - assertThat(customHandlerUrl.getPath()).isEqualTo(standardUrl.getPath()); - assertThat(customHandlerUrl.getQuery()).isEqualTo(standardUrl.getQuery()); - assertThat(customHandlerUrl.getRef()).isEqualTo(standardUrl.getRef()); - } - - private URL createUrl(String file) throws MalformedURLException { - return new URL("jar", null, -1, file, this.handler); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java deleted file mode 100644 index 15a57ab11cf..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileTests.java +++ /dev/null @@ -1,756 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FilePermission; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.attribute.FileTime; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.Random; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.stream.Stream; -import java.util.zip.CRC32; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; -import org.springframework.boot.loader.data.RandomAccessDataFile; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.StopWatch; -import org.springframework.util.StreamUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIOException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.spy; - -/** - * Tests for {@link JarFile}. - * - * @author Phillip Webb - * @author Martin Lau - * @author Andy Wilkinson - * @author Madhura Bhave - */ -@ExtendWith(JarUrlProtocolHandler.class) -class JarFileTests { - - private static final String PROTOCOL_HANDLER = "java.protocol.handler.pkgs"; - - private static final String HANDLERS_PACKAGE = "org.springframework.boot.loader"; - - @TempDir - File tempDir; - - private File rootJarFile; - - private JarFile jarFile; - - @BeforeEach - void setup() throws Exception { - this.rootJarFile = new File(this.tempDir, "root.jar"); - TestJarCreator.createTestJar(this.rootJarFile); - this.jarFile = new JarFile(this.rootJarFile); - } - - @AfterEach - void tearDown() throws Exception { - this.jarFile.close(); - } - - @Test - void jdkJarFile() throws Exception { - // Sanity checks to see how the default jar file operates - java.util.jar.JarFile jarFile = new java.util.jar.JarFile(this.rootJarFile); - assertThat(jarFile.getComment()).isEqualTo("outer"); - Enumeration entries = jarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/"); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF"); - assertThat(entries.nextElement().getName()).isEqualTo("1.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("2.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("d/"); - assertThat(entries.nextElement().getName()).isEqualTo("d/9.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("special/"); - assertThat(entries.nextElement().getName()).isEqualTo("special/\u00EB.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("another-nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("space nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("multi-release.jar"); - assertThat(entries.hasMoreElements()).isFalse(); - URL jarUrl = new URL("jar:" + this.rootJarFile.toURI() + "!/"); - URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { jarUrl }); - assertThat(urlClassLoader.getResource("special/\u00EB.dat")).isNotNull(); - assertThat(urlClassLoader.getResource("d/9.dat")).isNotNull(); - urlClassLoader.close(); - jarFile.close(); - } - - @Test - void createFromFile() throws Exception { - JarFile jarFile = new JarFile(this.rootJarFile); - assertThat(jarFile.getName()).isNotNull(); - jarFile.close(); - } - - @Test - void getManifest() throws Exception { - assertThat(this.jarFile.getManifest().getMainAttributes().getValue("Built-By")).isEqualTo("j1"); - } - - @Test - void getManifestEntry() throws Exception { - ZipEntry entry = this.jarFile.getJarEntry("META-INF/MANIFEST.MF"); - Manifest manifest = new Manifest(this.jarFile.getInputStream(entry)); - assertThat(manifest.getMainAttributes().getValue("Built-By")).isEqualTo("j1"); - } - - @Test - void getEntries() { - Enumeration entries = this.jarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/"); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF"); - assertThat(entries.nextElement().getName()).isEqualTo("1.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("2.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("d/"); - assertThat(entries.nextElement().getName()).isEqualTo("d/9.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("special/"); - assertThat(entries.nextElement().getName()).isEqualTo("special/\u00EB.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("another-nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("space nested.jar"); - assertThat(entries.nextElement().getName()).isEqualTo("multi-release.jar"); - assertThat(entries.hasMoreElements()).isFalse(); - } - - @Test - void getSpecialResourceViaClassLoader() throws Exception { - URLClassLoader urlClassLoader = new URLClassLoader(new URL[] { this.jarFile.getUrl() }); - assertThat(urlClassLoader.getResource("special/\u00EB.dat")).isNotNull(); - urlClassLoader.close(); - } - - @Test - void getJarEntry() { - java.util.jar.JarEntry entry = this.jarFile.getJarEntry("1.dat"); - assertThat(entry).isNotNull(); - assertThat(entry.getName()).isEqualTo("1.dat"); - } - - @Test - void getJarEntryWhenClosed() throws Exception { - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getJarEntry("1.dat")); - } - - @Test - void getInputStream() throws Exception { - InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("1.dat")); - assertThat(inputStream.available()).isOne(); - assertThat(inputStream.read()).isOne(); - assertThat(inputStream.available()).isZero(); - assertThat(inputStream.read()).isEqualTo(-1); - } - - @Test - void getInputStreamWhenClosed() throws Exception { - ZipEntry entry = this.jarFile.getEntry("1.dat"); - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getInputStream(entry)); - } - - @Test - void getComment() { - assertThat(this.jarFile.getComment()).isEqualTo("outer"); - } - - @Test - void getCommentWhenClosed() throws Exception { - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(() -> this.jarFile.getComment()); - } - - @Test - void getName() { - assertThat(this.jarFile.getName()).isEqualTo(this.rootJarFile.getPath()); - } - - @Test - void size() throws Exception { - try (ZipFile zip = new ZipFile(this.rootJarFile)) { - assertThat(this.jarFile).hasSize(zip.size()); - } - } - - @Test - void sizeWhenClosed() throws Exception { - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(() -> this.jarFile.size()); - } - - @Test - void getEntryTime() throws Exception { - java.util.jar.JarFile jdkJarFile = new java.util.jar.JarFile(this.rootJarFile); - assertThat(this.jarFile.getEntry("META-INF/MANIFEST.MF").getTime()) - .isEqualTo(jdkJarFile.getEntry("META-INF/MANIFEST.MF").getTime()); - jdkJarFile.close(); - } - - @Test - void close() throws Exception { - RandomAccessDataFile randomAccessDataFile = spy(new RandomAccessDataFile(this.rootJarFile)); - JarFile jarFile = new JarFile(randomAccessDataFile); - jarFile.close(); - then(randomAccessDataFile).should().close(); - } - - @Test - void getUrl() throws Exception { - URL url = this.jarFile.getUrl(); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/"); - JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); - assertThat(JarFileWrapper.unwrap(jarURLConnection.getJarFile())).isSameAs(this.jarFile); - assertThat(jarURLConnection.getJarEntry()).isNull(); - assertThat(jarURLConnection.getContentLength()).isGreaterThan(1); - assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) jarURLConnection.getContent())).isSameAs(this.jarFile); - assertThat(jarURLConnection.getContentType()).isEqualTo("x-java/jar"); - assertThat(jarURLConnection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI()); - } - - @Test - void createEntryUrl() throws Exception { - URL url = new URL(this.jarFile.getUrl(), "1.dat"); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/1.dat"); - JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); - assertThat(JarFileWrapper.unwrap(jarURLConnection.getJarFile())).isSameAs(this.jarFile); - assertThat(jarURLConnection.getJarEntry()).isSameAs(this.jarFile.getJarEntry("1.dat")); - assertThat(jarURLConnection.getContentLength()).isOne(); - assertThat(jarURLConnection.getContent()).isInstanceOf(InputStream.class); - assertThat(jarURLConnection.getContentType()).isEqualTo("content/unknown"); - assertThat(jarURLConnection.getPermission()).isInstanceOf(FilePermission.class); - FilePermission permission = (FilePermission) jarURLConnection.getPermission(); - assertThat(permission.getActions()).isEqualTo("read"); - assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath()); - } - - @Test - void getMissingEntryUrl() throws Exception { - URL url = new URL(this.jarFile.getUrl(), "missing.dat"); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/missing.dat"); - assertThatExceptionOfType(FileNotFoundException.class) - .isThrownBy(((JarURLConnection) url.openConnection())::getJarEntry); - } - - @Test - void getUrlStream() throws Exception { - URL url = this.jarFile.getUrl(); - url.openConnection(); - assertThatIOException().isThrownBy(url::openStream); - } - - @Test - void getEntryUrlStream() throws Exception { - URL url = new URL(this.jarFile.getUrl(), "1.dat"); - url.openConnection(); - try (InputStream stream = url.openStream()) { - assertThat(stream.read()).isOne(); - assertThat(stream.read()).isEqualTo(-1); - } - } - - @Test - void getNestedJarFile() throws Exception { - try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - assertThat(nestedJarFile.getComment()).isEqualTo("nested"); - Enumeration entries = nestedJarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/"); - assertThat(entries.nextElement().getName()).isEqualTo("META-INF/MANIFEST.MF"); - assertThat(entries.nextElement().getName()).isEqualTo("3.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("4.dat"); - assertThat(entries.nextElement().getName()).isEqualTo("\u00E4.dat"); - assertThat(entries.hasMoreElements()).isFalse(); - - InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("3.dat")); - assertThat(inputStream.read()).isEqualTo(3); - assertThat(inputStream.read()).isEqualTo(-1); - - URL url = nestedJarFile.getUrl(); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/"); - JarURLConnection conn = (JarURLConnection) url.openConnection(); - assertThat(JarFileWrapper.unwrap(conn.getJarFile())).isSameAs(nestedJarFile); - assertThat(conn.getJarFileURL()).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); - assertThat(conn.getInputStream()).isNotNull(); - JarInputStream jarInputStream = new JarInputStream(conn.getInputStream()); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("3.dat"); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("4.dat"); - assertThat(jarInputStream.getNextJarEntry().getName()).isEqualTo("\u00E4.dat"); - jarInputStream.close(); - assertThat(conn.getPermission()).isInstanceOf(FilePermission.class); - FilePermission permission = (FilePermission) conn.getPermission(); - assertThat(permission.getActions()).isEqualTo("read"); - assertThat(permission.getName()).isEqualTo(this.rootJarFile.getPath()); - } - } - - @Test - void getNestedJarDirectory() throws Exception { - try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("d/"))) { - Enumeration entries = nestedJarFile.entries(); - assertThat(entries.nextElement().getName()).isEqualTo("9.dat"); - assertThat(entries.hasMoreElements()).isFalse(); - - try (InputStream inputStream = nestedJarFile.getInputStream(nestedJarFile.getEntry("9.dat"))) { - assertThat(inputStream.read()).isEqualTo(9); - assertThat(inputStream.read()).isEqualTo(-1); - } - - URL url = nestedJarFile.getUrl(); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/d!/"); - JarURLConnection connection = (JarURLConnection) url.openConnection(); - assertThat(JarFileWrapper.unwrap(connection.getJarFile())).isSameAs(nestedJarFile); - } - } - - @Test - void getNestedJarEntryUrl() throws Exception { - try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - URL url = nestedJarFile.getJarEntry("3.dat").getUrl(); - assertThat(url).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat"); - try (InputStream inputStream = url.openStream()) { - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(3); - } - } - } - - @Test - void createUrlFromString() throws Exception { - String spec = "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat"; - URL url = new URL(spec); - assertThat(url).hasToString(spec); - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (InputStream inputStream = connection.getInputStream()) { - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(3); - assertThat(connection.getURL()).hasToString(spec); - assertThat(connection.getJarFileURL()).hasToString("jar:" + this.rootJarFile.toURI() + "!/nested.jar"); - assertThat(connection.getEntryName()).isEqualTo("3.dat"); - connection.getJarFile().close(); - } - } - - @Test - void createNonNestedUrlFromString() throws Exception { - nonNestedJarFileFromString("jar:" + this.rootJarFile.toURI() + "!/2.dat"); - } - - @Test - void createNonNestedUrlFromPathString() throws Exception { - nonNestedJarFileFromString("jar:" + this.rootJarFile.toPath().toUri() + "!/2.dat"); - } - - private void nonNestedJarFileFromString(String spec) throws Exception { - JarFile.registerUrlProtocolHandler(); - URL url = new URL(spec); - assertThat(url).hasToString(spec); - JarURLConnection connection = (JarURLConnection) url.openConnection(); - try (InputStream inputStream = connection.getInputStream()) { - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(2); - assertThat(connection.getURL()).hasToString(spec); - assertThat(connection.getJarFileURL().toURI()).isEqualTo(this.rootJarFile.toURI()); - assertThat(connection.getEntryName()).isEqualTo("2.dat"); - } - connection.getJarFile().close(); - } - - @Test - void getDirectoryInputStream() throws Exception { - InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d/")); - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(-1); - } - - @Test - void getDirectoryInputStreamWithoutSlash() throws Exception { - InputStream inputStream = this.jarFile.getInputStream(this.jarFile.getEntry("d")); - assertThat(inputStream).isNotNull(); - assertThat(inputStream.read()).isEqualTo(-1); - } - - @Test - void sensibleToString() throws Exception { - assertThat(this.jarFile).hasToString(this.rootJarFile.getPath()); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - assertThat(nested).hasToString(this.rootJarFile.getPath() + "!/nested.jar"); - } - } - - @Test - void verifySignedJar() throws Exception { - File signedJarFile = getSignedJarFile(); - assertThat(signedJarFile).exists(); - try (java.util.jar.JarFile expected = new java.util.jar.JarFile(signedJarFile)) { - try (JarFile actual = new JarFile(signedJarFile)) { - StopWatch stopWatch = new StopWatch(); - Enumeration actualEntries = actual.entries(); - while (actualEntries.hasMoreElements()) { - JarEntry actualEntry = actualEntries.nextElement(); - java.util.jar.JarEntry expectedEntry = expected.getJarEntry(actualEntry.getName()); - StreamUtils.drain(expected.getInputStream(expectedEntry)); - if (!actualEntry.getName().equals("META-INF/MANIFEST.MF")) { - assertThat(actualEntry.getCertificates()).as(actualEntry.getName()) - .isEqualTo(expectedEntry.getCertificates()); - assertThat(actualEntry.getCodeSigners()).as(actualEntry.getName()) - .isEqualTo(expectedEntry.getCodeSigners()); - } - } - assertThat(stopWatch.getTotalTimeSeconds()).isLessThan(3.0); - } - } - } - - private File getSignedJarFile() { - String[] entries = System.getProperty("java.class.path").split(System.getProperty("path.separator")); - for (String entry : entries) { - if (entry.contains("bcprov")) { - return new File(entry); - } - } - return null; - } - - @Test - void jarFileWithScriptAtTheStart() throws Exception { - File file = new File(this.tempDir, "test.jar"); - InputStream sourceJarContent = new FileInputStream(this.rootJarFile); - FileOutputStream outputStream = new FileOutputStream(file); - StreamUtils.copy("#/bin/bash", Charset.defaultCharset(), outputStream); - FileCopyUtils.copy(sourceJarContent, outputStream); - this.rootJarFile = file; - this.jarFile.close(); - this.jarFile = new JarFile(file); - // Call some other tests to verify - getEntries(); - getNestedJarFile(); - } - - @Test - void cannotLoadMissingJar() throws Exception { - // relates to gh-1070 - try (JarFile nestedJarFile = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - URL nestedUrl = nestedJarFile.getUrl(); - URL url = new URL(nestedUrl, nestedJarFile.getUrl() + "missing.jar!/3.dat"); - assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(url.openConnection()::getInputStream); - } - } - - @Test - void registerUrlProtocolHandlerWithNoExistingRegistration() { - String original = System.getProperty(PROTOCOL_HANDLER); - try { - System.clearProperty(PROTOCOL_HANDLER); - JarFile.registerUrlProtocolHandler(); - String protocolHandler = System.getProperty(PROTOCOL_HANDLER); - assertThat(protocolHandler).isEqualTo(HANDLERS_PACKAGE); - } - finally { - if (original == null) { - System.clearProperty(PROTOCOL_HANDLER); - } - else { - System.setProperty(PROTOCOL_HANDLER, original); - } - } - } - - @Test - void registerUrlProtocolHandlerAddsToExistingRegistration() { - String original = System.getProperty(PROTOCOL_HANDLER); - try { - System.setProperty(PROTOCOL_HANDLER, "com.example"); - JarFile.registerUrlProtocolHandler(); - String protocolHandler = System.getProperty(PROTOCOL_HANDLER); - assertThat(protocolHandler).isEqualTo("com.example|" + HANDLERS_PACKAGE); - } - finally { - if (original == null) { - System.clearProperty(PROTOCOL_HANDLER); - } - else { - System.setProperty(PROTOCOL_HANDLER, original); - } - } - } - - @Test - void jarFileCanBeDeletedOnceItHasBeenClosed() throws Exception { - File jar = new File(this.tempDir, "test.jar"); - TestJarCreator.createTestJar(jar); - JarFile jf = new JarFile(jar); - jf.close(); - assertThat(jar.delete()).isTrue(); - } - - @Test - void createUrlFromStringWithContextWhenNotFound() throws Exception { - // gh-12483 - JarURLConnection.setUseFastExceptions(true); - try { - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - URL context = nested.getUrl(); - new URL(context, "jar:" + this.rootJarFile.toURI() + "!/nested.jar!/3.dat").openConnection() - .getInputStream() - .close(); - assertThatExceptionOfType(FileNotFoundException.class) - .isThrownBy(new URL(context, "jar:" + this.rootJarFile.toURI() + "!/no.dat") - .openConnection()::getInputStream); - } - } - finally { - JarURLConnection.setUseFastExceptions(false); - } - } - - @Test - void multiReleaseEntry() throws Exception { - try (JarFile multiRelease = this.jarFile.getNestedJarFile(this.jarFile.getEntry("multi-release.jar"))) { - ZipEntry entry = multiRelease.getEntry("multi-release.dat"); - assertThat(entry.getName()).isEqualTo("multi-release.dat"); - InputStream inputStream = multiRelease.getInputStream(entry); - assertThat(inputStream.available()).isOne(); - assertThat(inputStream.read()).isEqualTo(Runtime.version().feature()); - } - } - - @Test - void zip64JarThatExceedsZipEntryLimitCanBeRead() throws Exception { - File zip64Jar = new File(this.tempDir, "zip64.jar"); - FileCopyUtils.copy(zip64Jar(), zip64Jar); - try (JarFile zip64JarFile = new JarFile(zip64Jar)) { - List entries = Collections.list(zip64JarFile.entries()); - assertThat(entries).hasSize(65537); - for (int i = 0; i < entries.size(); i++) { - JarEntry entry = entries.get(i); - InputStream entryInput = zip64JarFile.getInputStream(entry); - assertThat(entryInput).hasContent("Entry " + (i + 1)); - } - } - } - - @Test - void zip64JarThatExceedsZipSizeLimitCanBeRead() throws Exception { - Assumptions.assumeTrue(this.tempDir.getFreeSpace() > 6L * 1024 * 1024 * 1024, "Insufficient disk space"); - File zip64Jar = new File(this.tempDir, "zip64.jar"); - File entry = new File(this.tempDir, "entry.dat"); - CRC32 crc32 = new CRC32(); - try (FileOutputStream entryOut = new FileOutputStream(entry)) { - byte[] data = new byte[1024 * 1024]; - new Random().nextBytes(data); - for (int i = 0; i < 1024; i++) { - entryOut.write(data); - crc32.update(data); - } - } - try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(zip64Jar))) { - for (int i = 0; i < 6; i++) { - JarEntry storedEntry = new JarEntry("huge-" + i); - storedEntry.setSize(entry.length()); - storedEntry.setCompressedSize(entry.length()); - storedEntry.setCrc(crc32.getValue()); - storedEntry.setMethod(ZipEntry.STORED); - jarOutput.putNextEntry(storedEntry); - try (FileInputStream entryIn = new FileInputStream(entry)) { - StreamUtils.copy(entryIn, jarOutput); - } - jarOutput.closeEntry(); - } - } - try (JarFile zip64JarFile = new JarFile(zip64Jar)) { - assertThat(Collections.list(zip64JarFile.entries())).hasSize(6); - } - } - - @Test - void nestedZip64JarCanBeRead() throws Exception { - File outer = new File(this.tempDir, "outer.jar"); - try (JarOutputStream jarOutput = new JarOutputStream(new FileOutputStream(outer))) { - JarEntry nestedEntry = new JarEntry("nested-zip64.jar"); - byte[] contents = zip64Jar(); - nestedEntry.setSize(contents.length); - nestedEntry.setCompressedSize(contents.length); - CRC32 crc32 = new CRC32(); - crc32.update(contents); - nestedEntry.setCrc(crc32.getValue()); - nestedEntry.setMethod(ZipEntry.STORED); - jarOutput.putNextEntry(nestedEntry); - jarOutput.write(contents); - jarOutput.closeEntry(); - } - try (JarFile outerJarFile = new JarFile(outer)) { - try (JarFile nestedZip64JarFile = outerJarFile - .getNestedJarFile(outerJarFile.getJarEntry("nested-zip64.jar"))) { - List entries = Collections.list(nestedZip64JarFile.entries()); - assertThat(entries).hasSize(65537); - for (int i = 0; i < entries.size(); i++) { - JarEntry entry = entries.get(i); - InputStream entryInput = nestedZip64JarFile.getInputStream(entry); - assertThat(entryInput).hasContent("Entry " + (i + 1)); - } - } - } - } - - private byte[] zip64Jar() throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - JarOutputStream jarOutput = new JarOutputStream(bytes); - for (int i = 0; i < 65537; i++) { - jarOutput.putNextEntry(new JarEntry(i + ".dat")); - jarOutput.write(("Entry " + (i + 1)).getBytes(StandardCharsets.UTF_8)); - jarOutput.closeEntry(); - } - jarOutput.close(); - return bytes.toByteArray(); - } - - @Test - void jarFileEntryWithEpochTimeOfZeroShouldNotFail() throws Exception { - File file = createJarFileWithEpochTimeOfZero(); - try (JarFile jar = new JarFile(file)) { - Enumeration entries = jar.entries(); - JarEntry entry = entries.nextElement(); - assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH); - assertThat(entry.getName()).isEqualTo("1.dat"); - } - } - - @Test - void mismatchedStreamEntriesThrowsException() throws IOException { - File mismatchJar = new File("src/test/resources/jars/mismatch.jar"); - IllegalStateException failure = null; - try (JarFile jarFile = new JarFile(mismatchJar)) { - JarFile nestedJarFile = jarFile.getNestedJarFile(jarFile.getJarEntry("inner.jar")); - Enumeration entries = nestedJarFile.entries(); - while (entries.hasMoreElements()) { - try { - entries.nextElement().getCodeSigners(); - } - catch (IllegalStateException ex) { - failure = (failure != null) ? failure : ex; - } - } - } - assertThat(failure) - .hasMessage("Content mismatch when reading security info for entry 'content' (content check)"); - } - - private File createJarFileWithEpochTimeOfZero() throws Exception { - File jarFile = new File(this.tempDir, "temp.jar"); - FileOutputStream fileOutputStream = new FileOutputStream(jarFile); - String comment = "outer"; - try (JarOutputStream jarOutputStream = new JarOutputStream(fileOutputStream)) { - jarOutputStream.setComment(comment); - JarEntry entry = new JarEntry("1.dat"); - entry.setLastModifiedTime(FileTime.from(Instant.EPOCH)); - jarOutputStream.putNextEntry(entry); - jarOutputStream.write(new byte[] { (byte) 1 }); - jarOutputStream.closeEntry(); - } - - byte[] data = Files.readAllBytes(jarFile.toPath()); - int headerPosition = data.length - ZipFile.ENDHDR - comment.getBytes().length; - int centralHeaderPosition = (int) Bytes.littleEndianValue(data, headerPosition + ZipFile.ENDOFF, 1); - int localHeaderPosition = (int) Bytes.littleEndianValue(data, centralHeaderPosition + ZipFile.CENOFF, 1); - writeTimeBlock(data, centralHeaderPosition + ZipFile.CENTIM, 0); - writeTimeBlock(data, localHeaderPosition + ZipFile.LOCTIM, 0); - - File jar = new File(this.tempDir, "zerotimed.jar"); - Files.write(jar.toPath(), data); - return jar; - } - - private static void writeTimeBlock(byte[] data, int pos, int value) { - data[pos] = (byte) (value & 0xff); - data[pos + 1] = (byte) ((value >> 8) & 0xff); - data[pos + 2] = (byte) ((value >> 16) & 0xff); - data[pos + 3] = (byte) ((value >> 24) & 0xff); - } - - @Test - void iterator() { - Iterator iterator = this.jarFile.iterator(); - List names = new ArrayList<>(); - while (iterator.hasNext()) { - names.add(iterator.next().getName()); - } - assertThat(names).hasSize(12).contains("1.dat"); - } - - @Test - void iteratorWhenClosed() throws IOException { - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(() -> this.jarFile.iterator()); - } - - @Test - void iteratorWhenClosedLater() throws IOException { - Iterator iterator = this.jarFile.iterator(); - iterator.next(); - this.jarFile.close(); - assertThatZipFileClosedIsThrownBy(iterator::hasNext); - } - - @Test - void stream() { - Stream stream = this.jarFile.stream().map(JarEntry::getName); - assertThat(stream).hasSize(12).contains("1.dat"); - - } - - private void assertThatZipFileClosedIsThrownBy(ThrowingCallable throwingCallable) { - assertThatIllegalStateException().isThrownBy(throwingCallable).withMessage("zip file closed"); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileWrapperTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileWrapperTests.java deleted file mode 100644 index 7ef7ba7dd6d..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarFileWrapperTests.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.Permission; -import java.util.EnumSet; -import java.util.Enumeration; -import java.util.Set; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.jar.JarFileWrapperTests.SpyJarFile.Call; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link JarFileWrapper}. - * - * @author Phillip Webb - */ -class JarFileWrapperTests { - - private SpyJarFile parent; - - private JarFileWrapper wrapper; - - @BeforeEach - void setup(@TempDir File temp) throws Exception { - this.parent = new SpyJarFile(createTempJar(temp)); - this.wrapper = new JarFileWrapper(this.parent); - } - - @AfterEach - void cleanup() throws Exception { - this.parent.close(); - } - - private File createTempJar(File temp) throws IOException { - File file = new File(temp, "temp.jar"); - new JarOutputStream(new FileOutputStream(file)).close(); - return file; - } - - @Test - void getUrlDelegatesToParent() throws MalformedURLException { - this.wrapper.getUrl(); - this.parent.verify(Call.GET_URL); - } - - @Test - void getTypeDelegatesToParent() { - this.wrapper.getType(); - this.parent.verify(Call.GET_TYPE); - } - - @Test - void getPermissionDelegatesToParent() { - this.wrapper.getPermission(); - this.parent.verify(Call.GET_PERMISSION); - } - - @Test - void getManifestDelegatesToParent() throws IOException { - this.wrapper.getManifest(); - this.parent.verify(Call.GET_MANIFEST); - } - - @Test - void entriesDelegatesToParent() { - this.wrapper.entries(); - this.parent.verify(Call.ENTRIES); - } - - @Test - void getJarEntryDelegatesToParent() { - this.wrapper.getJarEntry("test"); - this.parent.verify(Call.GET_JAR_ENTRY); - } - - @Test - void getEntryDelegatesToParent() { - this.wrapper.getEntry("test"); - this.parent.verify(Call.GET_ENTRY); - } - - @Test - void getInputStreamDelegatesToParent() throws IOException { - this.wrapper.getInputStream(); - this.parent.verify(Call.GET_INPUT_STREAM); - } - - @Test - void getEntryInputStreamDelegatesToParent() throws IOException { - ZipEntry entry = new ZipEntry("test"); - this.wrapper.getInputStream(entry); - this.parent.verify(Call.GET_ENTRY_INPUT_STREAM); - } - - @Test - void getCommentDelegatesToParent() { - this.wrapper.getComment(); - this.parent.verify(Call.GET_COMMENT); - } - - @Test - void sizeDelegatesToParent() { - this.wrapper.size(); - this.parent.verify(Call.SIZE); - } - - @Test - void toStringDelegatesToParent() { - assertThat(this.wrapper.toString()).endsWith("temp.jar"); - } - - @Test // gh-22991 - void wrapperMustNotImplementClose() { - // If the wrapper overrides close then on Java 11 a FinalizableResource - // instance will be used to perform cleanup. This can result in a lot - // of additional memory being used since cleanup only occurs when the - // finalizer thread runs. See gh-22991 - assertThatExceptionOfType(NoSuchMethodException.class) - .isThrownBy(() -> JarFileWrapper.class.getDeclaredMethod("close")); - } - - @Test - void streamDelegatesToParent() { - this.wrapper.stream(); - this.parent.verify(Call.STREAM); - } - - /** - * {@link JarFile} that we can spy (even on Java 11+) - */ - static class SpyJarFile extends JarFile { - - private final Set calls = EnumSet.noneOf(Call.class); - - SpyJarFile(File file) throws IOException { - super(file); - } - - @Override - Permission getPermission() { - mark(Call.GET_PERMISSION); - return super.getPermission(); - } - - @Override - public Manifest getManifest() throws IOException { - mark(Call.GET_MANIFEST); - return super.getManifest(); - } - - @Override - public Enumeration entries() { - mark(Call.ENTRIES); - return super.entries(); - } - - @Override - public Stream stream() { - mark(Call.STREAM); - return super.stream(); - } - - @Override - public JarEntry getJarEntry(String name) { - mark(Call.GET_JAR_ENTRY); - return super.getJarEntry(name); - } - - @Override - public ZipEntry getEntry(String name) { - mark(Call.GET_ENTRY); - return super.getEntry(name); - } - - @Override - InputStream getInputStream() throws IOException { - mark(Call.GET_INPUT_STREAM); - return super.getInputStream(); - } - - @Override - InputStream getInputStream(String name) throws IOException { - mark(Call.GET_ENTRY_INPUT_STREAM); - return super.getInputStream(name); - } - - @Override - public String getComment() { - mark(Call.GET_COMMENT); - return super.getComment(); - } - - @Override - public int size() { - mark(Call.SIZE); - return super.size(); - } - - @Override - public URL getUrl() throws MalformedURLException { - mark(Call.GET_URL); - return super.getUrl(); - } - - @Override - JarFileType getType() { - mark(Call.GET_TYPE); - return super.getType(); - } - - private void mark(Call call) { - this.calls.add(call); - } - - void verify(Call call) { - assertThat(call).matches(this.calls::contains); - } - - enum Call { - - GET_URL, - - GET_TYPE, - - GET_PERMISSION, - - GET_MANIFEST, - - ENTRIES, - - GET_JAR_ENTRY, - - GET_ENTRY, - - GET_INPUT_STREAM, - - GET_ENTRY_INPUT_STREAM, - - GET_COMMENT, - - SIZE, - - STREAM - - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java deleted file mode 100644 index f9126afc61a..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarURLConnectionTests.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.net.URL; -import java.util.List; -import java.util.jar.JarEntry; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.boot.loader.TestJarCreator; -import org.springframework.boot.loader.jar.JarURLConnection.JarEntryName; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link JarURLConnection}. - * - * @author Andy Wilkinson - * @author Phillip Webb - * @author Rostyslav Dudka - */ -class JarURLConnectionTests { - - private File rootJarFile; - - private JarFile jarFile; - - @BeforeEach - void setup(@TempDir File tempDir) throws Exception { - this.rootJarFile = new File(tempDir, "root.jar"); - TestJarCreator.createTestJar(this.rootJarFile); - this.jarFile = new JarFile(this.rootJarFile); - } - - @AfterEach - void tearDown() throws Exception { - this.jarFile.close(); - } - - @Test - void connectionToRootUsingAbsoluteUrl() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/"); - Object content = JarURLConnection.get(url, this.jarFile).getContent(); - assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) content)).isSameAs(this.jarFile); - } - - @Test - void connectionToRootUsingRelativeUrl() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/"); - Object content = JarURLConnection.get(url, this.jarFile).getContent(); - assertThat(JarFileWrapper.unwrap((java.util.jar.JarFile) content)).isSameAs(this.jarFile); - } - - @Test - void connectionToEntryUsingAbsoluteUrl() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat"); - try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 1 }); - } - } - - @Test - void connectionToEntryUsingRelativeUrl() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/1.dat"); - try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 1 }); - } - } - - @Test - void connectionToEntryUsingAbsoluteUrlWithFileColonSlashSlashPrefix() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat"); - try (InputStream input = JarURLConnection.get(url, this.jarFile).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 1 }); - } - } - - @Test - void connectionToEntryUsingAbsoluteUrlForNestedEntry() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - try (InputStream input = connection.getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - connection.getJarFile().close(); - } - - @Test - void connectionToEntryUsingRelativeUrlForNestedEntry() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - try (InputStream input = connection.getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - connection.getJarFile().close(); - } - - @Test - void connectionToEntryUsingAbsoluteUrlForEntryFromNestedJarFile() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - } - } - - @Test - void connectionToEntryUsingRelativeUrlForEntryFromNestedJarFile() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/nested.jar!/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - } - } - - @Test - void connectionToEntryInNestedJarFromUrlThatUsesExistingUrlAsContext() throws Exception { - URL url = new URL(new URL("jar", null, -1, this.rootJarFile.toURI().toURL() + "!/nested.jar!/", new Handler()), - "/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - try (InputStream input = JarURLConnection.get(url, nested).getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - } - } - - @Test - void connectionToEntryWithSpaceNestedEntry() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/space nested.jar!/3.dat"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - try (InputStream input = connection.getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - connection.getJarFile().close(); - } - - @Test - void connectionToEntryWithEncodedSpaceNestedEntry() throws Exception { - URL url = new URL("jar:file:" + getRelativePath() + "!/space%20nested.jar!/3.dat"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - try (InputStream input = connection.getInputStream()) { - assertThat(input).hasBinaryContent(new byte[] { 3 }); - } - connection.getJarFile().close(); - } - - @Test - void connectionToEntryUsingWrongAbsoluteUrlForEntryFromNestedJarFile() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/w.jar!/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - assertThatExceptionOfType(FileNotFoundException.class) - .isThrownBy(JarURLConnection.get(url, nested)::getInputStream); - } - } - - @Test - void getContentLengthReturnsLengthOfUnderlyingEntry() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - JarURLConnection connection = JarURLConnection.get(url, nested); - assertThat(connection.getContentLength()).isOne(); - } - } - - @Test - void getContentLengthLongReturnsLengthOfUnderlyingEntry() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/nested.jar!/3.dat"); - try (JarFile nested = this.jarFile.getNestedJarFile(this.jarFile.getEntry("nested.jar"))) { - JarURLConnection connection = JarURLConnection.get(url, nested); - assertThat(connection.getContentLengthLong()).isOne(); - } - } - - @Test - void getLastModifiedReturnsLastModifiedTimeOfJarEntry() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/1.dat"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - assertThat(connection.getLastModified()).isEqualTo(connection.getJarEntry().getTime()); - } - - @Test - void entriesCanBeStreamedFromJarFileOfConnection() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - List entryNames = connection.getJarFile().stream().map(JarEntry::getName).toList(); - assertThat(entryNames).hasSize(12); - } - - @Test - void jarEntryBasicName() { - assertThat(new JarEntryName(new StringSequence("a/b/C.class"))).hasToString("a/b/C.class"); - } - - @Test - void jarEntryNameWithSingleByteEncodedCharacters() { - assertThat(new JarEntryName(new StringSequence("%61/%62/%43.class"))).hasToString("a/b/C.class"); - } - - @Test - void jarEntryNameWithDoubleByteEncodedCharacters() { - assertThat(new JarEntryName(new StringSequence("%c3%a1/b/C.class"))).hasToString("\u00e1/b/C.class"); - } - - @Test - void jarEntryNameWithMixtureOfEncodedAndUnencodedDoubleByteCharacters() { - assertThat(new JarEntryName(new StringSequence("%c3%a1/b/\u00c7.class"))).hasToString("\u00e1/b/\u00c7.class"); - } - - @Test - void openConnectionCanBeClosedWithoutClosingSourceJar() throws Exception { - URL url = new URL("jar:" + this.rootJarFile.toURI().toURL() + "!/"); - JarURLConnection connection = JarURLConnection.get(url, this.jarFile); - java.util.jar.JarFile connectionJarFile = connection.getJarFile(); - connectionJarFile.close(); - assertThat(this.jarFile.isClosed()).isFalse(); - } - - private String getRelativePath() { - return this.rootJarFile.getPath().replace('\\', '/'); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java deleted file mode 100644 index ee43c0df18d..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/JarUrlProtocolHandler.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import java.io.File; -import java.lang.ref.SoftReference; -import java.util.Map; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.Extension; -import org.junit.jupiter.api.extension.ExtensionContext; - -import org.springframework.test.util.ReflectionTestUtils; - -/** - * JUnit 5 {@link Extension} for tests that interact with Spring Boot's {@link Handler} - * for {@code jar:} URLs. Ensures that the handler is registered prior to test execution - * and cleans up the handler's root file cache afterwards. - * - * @author Andy Wilkinson - */ -class JarUrlProtocolHandler implements BeforeEachCallback, AfterEachCallback { - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - JarFile.registerUrlProtocolHandler(); - } - - @Override - @SuppressWarnings("unchecked") - public void afterEach(ExtensionContext context) throws Exception { - Map rootFileCache = ((SoftReference>) ReflectionTestUtils - .getField(Handler.class, "rootFileCache")).get(); - if (rootFileCache != null) { - for (JarFile rootJarFile : rootFileCache.values()) { - rootJarFile.close(); - } - rootFileCache.clear(); - } - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/StringSequenceTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/StringSequenceTests.java deleted file mode 100644 index 1eb171fdf0d..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jar/StringSequenceTests.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jar; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNullPointerException; - -/** - * Tests for {@link StringSequence}. - * - * @author Phillip Webb - */ -class StringSequenceTests { - - @Test - void createWhenSourceIsNullShouldThrowException() { - assertThatNullPointerException().isThrownBy(() -> new StringSequence(null)) - .withMessage("Source must not be null"); - } - - @Test - void createWithIndexWhenSourceIsNullShouldThrowException() { - assertThatNullPointerException().isThrownBy(() -> new StringSequence(null, 0, 0)) - .withMessage("Source must not be null"); - } - - @Test - void createWhenStartIsLessThanZeroShouldThrowException() { - assertThatExceptionOfType(StringIndexOutOfBoundsException.class) - .isThrownBy(() -> new StringSequence("x", -1, 0)); - } - - @Test - void createWhenEndIsGreaterThanLengthShouldThrowException() { - assertThatExceptionOfType(StringIndexOutOfBoundsException.class) - .isThrownBy(() -> new StringSequence("x", 0, 2)); - } - - @Test - void createFromString() { - assertThat(new StringSequence("test")).hasToString("test"); - } - - @Test - void subSequenceWithJustStartShouldReturnSubSequence() { - assertThat(new StringSequence("smiles").subSequence(1)).hasToString("miles"); - } - - @Test - void subSequenceShouldReturnSubSequence() { - assertThat(new StringSequence("hamburger").subSequence(4, 8)).hasToString("urge"); - assertThat(new StringSequence("smiles").subSequence(1, 5)).hasToString("mile"); - } - - @Test - void subSequenceWhenCalledMultipleTimesShouldReturnSubSequence() { - assertThat(new StringSequence("hamburger").subSequence(4, 8).subSequence(1, 3)).hasToString("rg"); - } - - @Test - void subSequenceWhenEndPastExistingEndShouldThrowException() { - StringSequence sequence = new StringSequence("abcde").subSequence(1, 4); - assertThat(sequence).hasToString("bcd"); - assertThat(sequence.subSequence(2, 3)).hasToString("d"); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> sequence.subSequence(3, 4)); - } - - @Test - void subSequenceWhenStartPastExistingEndShouldThrowException() { - StringSequence sequence = new StringSequence("abcde").subSequence(1, 4); - assertThat(sequence).hasToString("bcd"); - assertThat(sequence.subSequence(2, 3)).hasToString("d"); - assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> sequence.subSequence(4, 3)); - } - - @Test - void isEmptyWhenEmptyShouldReturnTrue() { - assertThat(new StringSequence("")).isEmpty(); - } - - @Test - void isEmptyWhenNotEmptyShouldReturnFalse() { - assertThat(new StringSequence("x")).isNotEmpty(); - } - - @Test - void lengthShouldReturnLength() { - StringSequence sequence = new StringSequence("hamburger"); - assertThat(sequence).hasSize(9); - assertThat(sequence.subSequence(4, 8)).hasSize(4); - } - - @Test - void charAtShouldReturnChar() { - StringSequence sequence = new StringSequence("hamburger"); - assertThat(sequence.charAt(0)).isEqualTo('h'); - assertThat(sequence.charAt(1)).isEqualTo('a'); - assertThat(sequence.subSequence(4, 8).charAt(0)).isEqualTo('u'); - assertThat(sequence.subSequence(4, 8).charAt(1)).isEqualTo('r'); - } - - @Test - void indexOfCharShouldReturnIndexOf() { - StringSequence sequence = new StringSequence("aabbaacc"); - assertThat(sequence.indexOf('a')).isZero(); - assertThat(sequence.indexOf('b')).isEqualTo(2); - assertThat(sequence.subSequence(2).indexOf('a')).isEqualTo(2); - } - - @Test - void indexOfStringShouldReturnIndexOf() { - StringSequence sequence = new StringSequence("aabbaacc"); - assertThat(sequence.indexOf('a')).isZero(); - assertThat(sequence.indexOf('b')).isEqualTo(2); - assertThat(sequence.subSequence(2).indexOf('a')).isEqualTo(2); - } - - @Test - void indexOfStringFromIndexShouldReturnIndexOf() { - StringSequence sequence = new StringSequence("aabbaacc"); - assertThat(sequence.indexOf("a", 2)).isEqualTo(4); - assertThat(sequence.indexOf("b", 3)).isEqualTo(3); - assertThat(sequence.subSequence(2).indexOf("a", 3)).isEqualTo(3); - } - - @Test - void hashCodeShouldBeSameAsString() { - assertThat(new StringSequence("hamburger")).hasSameHashCodeAs("hamburger"); - assertThat(new StringSequence("hamburger").subSequence(4, 8)).hasSameHashCodeAs("urge"); - } - - @Test - void equalsWhenSameContentShouldMatch() { - StringSequence a = new StringSequence("hamburger").subSequence(4, 8); - StringSequence b = new StringSequence("urge"); - StringSequence c = new StringSequence("urgh"); - assertThat(a).isEqualTo(b).isNotEqualTo(c); - } - - @Test - void notEqualsWhenSequencesOfDifferentLength() { - StringSequence a = new StringSequence("abcd"); - StringSequence b = new StringSequence("ef"); - assertThat(a).isNotEqualTo(b); - } - - @Test - void startsWithWhenExactMatch() { - assertThat(new StringSequence("abc").startsWith("abc")).isTrue(); - } - - @Test - void startsWithWhenLongerAndStartsWith() { - assertThat(new StringSequence("abcd").startsWith("abc")).isTrue(); - } - - @Test - void startsWithWhenLongerAndDoesNotStartWith() { - assertThat(new StringSequence("abcd").startsWith("abx")).isFalse(); - } - - @Test - void startsWithWhenShorterAndDoesNotStartWith() { - assertThat(new StringSequence("ab").startsWith("abc")).isFalse(); - assertThat(new StringSequence("ab").startsWith("c")).isFalse(); - } - - @Test - void startsWithOffsetWhenExactMatch() { - assertThat(new StringSequence("xabc").startsWith("abc", 1)).isTrue(); - } - - @Test - void startsWithOffsetWhenLongerAndStartsWith() { - assertThat(new StringSequence("xabcd").startsWith("abc", 1)).isTrue(); - } - - @Test - void startsWithOffsetWhenLongerAndDoesNotStartWith() { - assertThat(new StringSequence("xabcd").startsWith("abx", 1)).isFalse(); - } - - @Test - void startsWithOffsetWhenShorterAndDoesNotStartWith() { - assertThat(new StringSequence("xab").startsWith("abc", 1)).isFalse(); - assertThat(new StringSequence("xab").startsWith("c", 1)).isFalse(); - } - - @Test - void startsWithOnSubstringTailWhenMatch() { - StringSequence subSequence = new StringSequence("xabc").subSequence(1); - assertThat(subSequence.startsWith("abc")).isTrue(); - assertThat(subSequence.startsWith("abcd")).isFalse(); - } - - @Test - void startsWithOnSubstringMiddleWhenMatch() { - StringSequence subSequence = new StringSequence("xabc").subSequence(1, 3); - assertThat(subSequence.startsWith("ab")).isTrue(); - assertThat(subSequence.startsWith("abc")).isFalse(); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jarmode/LauncherJarModeTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jarmode/LauncherJarModeTests.java deleted file mode 100644 index 0e20ca0f1ea..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/jarmode/LauncherJarModeTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.jarmode; - -import java.util.Collections; -import java.util.Iterator; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.boot.loader.Launcher; -import org.springframework.boot.loader.archive.Archive; -import org.springframework.boot.testsupport.system.CapturedOutput; -import org.springframework.boot.testsupport.system.OutputCaptureExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link Launcher} with jar mode support. - * - * @author Phillip Webb - */ -@ExtendWith(OutputCaptureExtension.class) -class LauncherJarModeTests { - - @BeforeEach - void setup() { - System.setProperty(JarModeLauncher.DISABLE_SYSTEM_EXIT, "true"); - } - - @AfterEach - void cleanup() { - System.clearProperty("jarmode"); - System.clearProperty(JarModeLauncher.DISABLE_SYSTEM_EXIT); - } - - @Test - void launchWhenJarModePropertyIsSetLaunchesJarMode(CapturedOutput out) throws Exception { - System.setProperty("jarmode", "test"); - new TestLauncher().launch(new String[] { "boot" }); - assertThat(out).contains("running in test jar mode [boot]"); - assertThat(System.getProperty(JarModeLauncher.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("0"); - } - - @Test - void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception { - System.setProperty("jarmode", "idontexist"); - new TestLauncher().launch(new String[] { "boot" }); - assertThat(out).contains("Unsupported jarmode 'idontexist'"); - assertThat(System.getProperty(JarModeLauncher.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1"); - } - - @Test - void launchWhenJarModeRunFailsWithErrorExceptionPrintsSimpleMessage(CapturedOutput out) throws Exception { - System.setProperty("jarmode", "test"); - new TestLauncher().launch(new String[] { "error" }); - assertThat(out).contains("running in test jar mode [error]"); - assertThat(out).contains("Error: error message"); - assertThat(System.getProperty(JarModeLauncher.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1"); - } - - @Test - void launchWhenJarModeRunFailsWithErrorExceptionPrintsStackTrace(CapturedOutput out) throws Exception { - System.setProperty("jarmode", "test"); - new TestLauncher().launch(new String[] { "fail" }); - assertThat(out).contains("running in test jar mode [fail]"); - assertThat(out).contains("java.lang.IllegalStateException: bad"); - assertThat(System.getProperty(JarModeLauncher.SUPPRESSED_SYSTEM_EXIT_CODE)).isEqualTo("1"); - } - - private static final class TestLauncher extends Launcher { - - @Override - protected String getMainClass() throws Exception { - throw new IllegalStateException("Should not be called"); - } - - @Override - protected Iterator getClassPathArchivesIterator() throws Exception { - return Collections.emptyIterator(); - } - - @Override - protected void launch(String[] args) throws Exception { - super.launch(args); - } - - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/util/SystemPropertyUtilsTests.java b/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/util/SystemPropertyUtilsTests.java deleted file mode 100644 index d3a7eab0294..00000000000 --- a/loader/spring-boot-loader-classic/src/test/java/org/springframework/boot/loader/util/SystemPropertyUtilsTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.util; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link SystemPropertyUtils}. - * - * @author Dave Syer - */ -class SystemPropertyUtilsTests { - - @BeforeEach - void init() { - System.setProperty("foo", "bar"); - } - - @AfterEach - void close() { - System.clearProperty("foo"); - } - - @Test - void testVanillaPlaceholder() { - assertThat(SystemPropertyUtils.resolvePlaceholders("${foo}")).isEqualTo("bar"); - } - - @Test - void testDefaultValue() { - assertThat(SystemPropertyUtils.resolvePlaceholders("${bar:foo}")).isEqualTo("foo"); - } - - @Test - void testNestedPlaceholder() { - assertThat(SystemPropertyUtils.resolvePlaceholders("${bar:${spam:foo}}")).isEqualTo("foo"); - } - - @Test - void testEnvVar() { - assertThat(SystemPropertyUtils.getProperty("lang")).isEqualTo(System.getenv("LANG")); - } - -} diff --git a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/application.properties b/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/application.properties deleted file mode 100644 index 85a390f4d4e..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/application.properties +++ /dev/null @@ -1 +0,0 @@ -loader.main: demo.Application diff --git a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/bar.properties b/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/bar.properties deleted file mode 100644 index 6b37480f8b9..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/bar.properties +++ /dev/null @@ -1 +0,0 @@ -loader.main: my.BootInfBarApplication diff --git a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/foo.properties b/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/foo.properties deleted file mode 100644 index 36bd211df41..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/foo.properties +++ /dev/null @@ -1,3 +0,0 @@ -foo: Application -loader.main: my.${foo} -loader.path: etc diff --git a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/loader.properties b/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/loader.properties deleted file mode 100644 index 85a390f4d4e..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/BOOT-INF/classes/loader.properties +++ /dev/null @@ -1 +0,0 @@ -loader.main: demo.Application diff --git a/loader/spring-boot-loader-classic/src/test/resources/META-INF/spring.factories b/loader/spring-boot-loader-classic/src/test/resources/META-INF/spring.factories deleted file mode 100644 index c45c87d76f4..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/META-INF/spring.factories +++ /dev/null @@ -1,3 +0,0 @@ -# Jar Modes -org.springframework.boot.loader.jarmode.JarMode=\ -org.springframework.boot.loader.jarmode.TestJarMode \ No newline at end of file diff --git a/loader/spring-boot-loader-classic/src/test/resources/bar.properties b/loader/spring-boot-loader-classic/src/test/resources/bar.properties deleted file mode 100644 index 8301c2649f3..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/bar.properties +++ /dev/null @@ -1 +0,0 @@ -loader.main: my.BarApplication diff --git a/loader/spring-boot-loader-classic/src/test/resources/explodedsample/ExampleClass.txt b/loader/spring-boot-loader-classic/src/test/resources/explodedsample/ExampleClass.txt deleted file mode 100644 index c53100f90fa..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/explodedsample/ExampleClass.txt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2012-2020 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package explodedsample; - -/** - * Example class used to test class loading. - * - * @author Phillip Webb - */ -public class ExampleClass { - -} diff --git a/loader/spring-boot-loader-classic/src/test/resources/home/loader.properties b/loader/spring-boot-loader-classic/src/test/resources/home/loader.properties deleted file mode 100644 index 7a134969b76..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/home/loader.properties +++ /dev/null @@ -1 +0,0 @@ -loader.main: demo.HomeApplication diff --git a/loader/spring-boot-loader-classic/src/test/resources/jars/app.jar b/loader/spring-boot-loader-classic/src/test/resources/jars/app.jar deleted file mode 100644 index fb02c027012d66154056f7e7df17ba836ee6d2b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2213 zcmWIWW@Zs#;Nak3&?ukh!hi%g8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1ge|^G~JD{Zqb94n||$D z(D61fRetw|w~lFAvv#y+uRVA|;M5l0_^WGPN-h_^_Iq;SBBlGxlb4vEKK)8y_N^&Z zjG*Y)@p4|!I-oBk86hD7iA!a)kl;n~XL3$radB>-t^Z*I5u4=~mMBiSbm>#%0_LE^ z47OuRDlJ!5GPwwHPi9_Ll=&InLf~u=Nl}uDr0I+-r-AUK+P~A1r*5WP5;Z7Eh|s z4K?R3GwZp1-Der1Z=@AePHHhpS@GwlO|RRdTa)gY?DWW<&3!cH?vv?i`@Lq=|892w zBofSW=)!`3!VOm*N*YF?0binLOYR)_7$Oe~u{kG-Fg+n^F{ERQx%B(8!x2pY> z9h4f9{{7y8c`ut2`_4V`7xw2c_|JWJVQ72SOIE+FAG&7R@35Dw5RPMi+IfmeYrjkP z0dr7Xy-`}Rst_1Y8o>O6NVQjL=7Ewj9IygeDXF>nNXhmLni4UX632pqoXq6JlFa-( zJxII;#CkJ1ir6k*z4-OCWtsDr*|J=7=H4si=yI_+B;ks)|6{8YC*GXfv@XzowH#N~ z3-&LI7wwi#bz&~IKWF>=+}_V0e}3b8z>q6lsUsTby0#%zy41uX%py$kQ@d8cM?vRp zo2w5e%$>0?MdN#jg8s694sO?z7I1E?S;*Ap_I#bW*!M$Ml_YllwFo-6Vm8Oy%k2Jb z8;uUOB=>&WH`8sNO>pOqBAK?RN+r$r>P74_tbL{I(l=5x=6bL7{e8`)>cqv84Xt7; z+b)^Elh<_Iyj`C?`Q=fbt&(4?56Nu&Z_&BbpL>?(#n<;AYd?;=_{K0-q4oaXOk;lI z4+bYqOxKkEyDfeD#Kr4s>de!0T(@~dPPS2=f4iEsdQn4NOO73@v5V$GPumTXy&4}G z8(n?Fy+-oxGNFXP>oQ`OIua-5S>^`)WCF#K;p18cdtfX*K*>?3(V{>UTNHqb5Z`WF zzC#8)Zr|q|dUZu_uY)?9$~Qiq4n>u~j)ElZGH#Bq?^uo;xV>-w@oRT;cpf+}JNcpL z;FALjMH^*{xx<7-D8O6o$=K@uYc;A_o?&x7rk|KPx_xb?|s@w zU)Squ@mX)5Q=X+PU6#&Ag}jYEf6htXA;it~<<6(iJHh32T-xN*pfcJM>=Z^O5eC${ z23C=P>Kar4cN(bX3GhbMid_7IY8nKv1v24Uk?I|g30w@gs|kb&hk#5-rGc;$RBa#^ zw4iDO0iFPvARVwu1lhHqf*3j5g9>5YbK)R1Eyl++O5KF|nw6rm@) T0B=?{kWx+{R0XDvEHDoMzsJ<_ diff --git a/loader/spring-boot-loader-classic/src/test/resources/jars/mismatch.jar b/loader/spring-boot-loader-classic/src/test/resources/jars/mismatch.jar deleted file mode 100644 index 1f096171614ab46183abcf0a709d1ece07a019e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4953 zcmc(jc{J2-`^U$==aDT@L?p{FC|eYTGRPK5mcbYr#%_o~B#||Y##ZwvB9VQ|Uc`)j z&oas~WzRB{?HTnezI}V1^ZVmD=eg!w=iKM>xv%TK@Av1NdB4uB52m7K1OVs(fK=n{ z>wp8n0H6lwY8b0YUeVK(+Gzy<^#3yTuC3ZhW%BgBmZ=xi16|`&BDmS zxW8Bmd0*eW;m}St0AMc}$cs555KwpEEr|R6Gz8QCWBVKcdU|>Q>bI%?Zu={`c$^Mc z64=E8mK1BpcN7^)_sWh>xdXLzC9ddpdF4o+5LdeaiHT3A6PMg+{1j%6Vv;{s+GSF;k-Ke~%V>D$$imanf36ngyiY!P1UdH({pXXA<=OubYC2ag zY5%UhXE95)u^uNvp$u z*XojynyiCO%A2O_zUeMfT2fq^qZ+$;5jj=Zg)&amQb_T_)Klne#blY-g~UTB}#!gUTH#WZ8w-*~5mJl#$@#LhmB&&2f z_78RcfN0&PKe`lhsr;_uKV_IW)D0hits~0WA~u+PE(qcqa-$V7E!nj0A75*V&`xV| z-J905Z)!3S$iv==<4FKpHOqmNdF4Qu!UC$0`BWMvq1cT2-Z$^wby_*z_8{gYZJO53 zKUZ4hDW5MI>+_Mzc)H)d{iF8#xU#utMLvmmL)Xx+QM>=IY zNv%1xM4B}YNEHr6UbKzdk@8Xl4nIwdx6oDK-ooMC9&CI-nPT0d6^WF0mZFRX(xLjL zqnzD^x<&yAZ9-w%$Gp613R?v4B@cj{$j~bl8G<^v<*_Qbh$1*OvNzy0erDp z(zAw`MtTel9_?GPX=N44E9Rf@G0PYAxJXJ^{(O#!)XT3OlScC0c!f5*A@c4f81@i4 zxaR!6us>pPeNkmMF`#hEXVx!6x2Y=qEZ?BV8Ck9)n#?vh8$ph9l#E;27_O_Vun%V{ zpio0M>B-jkXLO#|R*!F7^%EhYAi)D;XSSyo&#FFh8T4$i4_^7qv3-cP`EJ?yt8NGr zQFGO12h6uq$g1%9BJN|p3x%dw&>I-sGbGw9e~Yn7JWXnQgWGPEN73u8F<-VqKQ5=n z!XDj+KrDnQ0eN~P9$TrJx|?4R^GfvIlz+@zGVI;FR(%n6*3ZSaO0Cwu0Vmu8l!{`B z4%lv9Yn5+tA(gry%gl3*e|x(8WJF&UsjYR)s-0MLW_1MNO=K$9>i>NtU z`evHb_Cy))Wan4)sSKDKeV)!+XkJ06+F9DO_r62r+HPi)wcD@~9uR7pN8Y?^0_aIt z)XSE=+(oMS*6wr-I(ZTM`E@E!P$cn1#&{?oHkRB5VELbC*zXoj^y90ER%!AjLhvE` zy!%mKrr;HbQReuzJUhwcff7;~XDu6D7^zG&Zy6TvGK;7#AIu1t@a?mXqlrHm@xVC! zm=wp8QJ!~VI=0#JaBAiIZI4CkJoPAe1zn= zbFH}{X|rSFCUTpDkLM-Yd6I)Gc3cTE(|6wJ(6MXKP9?V4ZD|d7_>P`9oOS3hoD;IZ z&IV*II&~^X(nKy+K9!ZU>1ju4dyMAiELtkp4_vYEKl)_NqT!W&Oj*&w&+EVNt=$8fWSibqZFJQv|ivo0k?T>Ef8kEs$MK;&W}xfs~@g$2ex)XjVmssIl%iSIHwv|S@#jD zHQvzO$9^Qhuhh4y_Ms+IAeSGNxd_jLM;Z_P^q6>cJpDX_>(N@}F~~7F63Qt+i$nO$ z9W0^D5M^WsNiNriB~#c&*FR9srZQ#SeBk=oot*x*u!g;ja!q9rgF}gA(l7%6^=n{98HMWDcJFPeSe2 z$B=dVKiTxbG(pO*Oh_Wp(88i=TV0wK3!|ddayloo;O% zkn&_)@$f9w5o>Af>tNDuk#+TsD*j0|g5`!Sf2W$?Pr^z*^{;QBepUTD(}1srTsRW? zL4{!#f=;#v^8oQaQ+jkeASpmNqQ`hfHSOIdu4yar?I`5ijOljZQ8YK@+72+ zcfDdM*}hAdN!rCNixqq-=TgQud7@C~fD2@YPlfD@^$H{awYcAtvzdvdpt|z|YnX0@rZ17D_$$`@F4jMO7wZSq%-T=%-db_k zY{$K8FMbmKvWc9*;c;EDgrKA-S!`PfC=nAH9+y}j#EXq}Q)9=S2@yeqFWf8(!i2|# zhsS^DsPKp`tco{_TLsBdtU-@59$6{?g%!SHC<>c~5~q@g^_LW+FAcH@2Gec{umK^_ z&+(L@vArJ;KN3XT6+BJ_jHNGh3wM zi`S#o)yZ_4tCLoS)i=;jI!K=Z$;hmlCXbC&%uMa{xhUtf`_UpcbCI6uYqveZx$~oS z;>$MSO{5V;*C{pzTimhK4%V0P4C&P)DwS)&Gv;XpxCM(4Tw z@q;C`_3s^8uyl_jM4nH79YUIUT$ea^cxe(6lQ8}9{iYjJ+M;jPHVk8pTzma!x$(aE ze6xaOzj*6NtjaTgrH){gwXkm6EYxw;TqqjNAT!X7vE%VZrIhBDeB9jj>j-@ISmONH zw=swKFL_xkbflYYku>+%LIw*T(bL2PQPvt}zU^r=^U+0^VT}2hE0*m(Q&@0T)9v)6 zxA2Q zt~oG&89F-p#I|3&wqZe=2tL0pX)W!MIQ8(0LC#G%8d<@Zn(d<#TBC~_e!~}UFc&38E~v27RHa|hPccpZjIQ?-N(`KtMR;|r^&3}K zyx9EmI)xRJG(>&Ioqu+4=hMrg!N&EEWs4wriWOc_fyvBwcSpEBjYpJ@SmC?3otv7| zklQ@mTu3z20OO9iNxy5|{OWAQu6*_Q%vs9{fvjtSiimoddf%QhR8(F{;bc~ORN!1h zfJ8X9Xpt%P;ft0D!j0E31SViOcoWC1hmVb6xmBdl&?Y;T)whn0UApOgFMa6VLKa8H zOf6yT_KY8iHL4}Q2DUD>&2?f$0t0F4g0++U-Ky+EGjFQCFf6dCkgZk!5E(YT4ChmL zu_ff}lECjzw2}b&*5nz!{T5_3>EV*?H@*8dJQ#>n$GWWI~VyBgC4LR5v(PPNa{Iw}R(gZ7KaFM&E84_w@NJvOmrqe10MWoDiC zaLSg=maPS2o|vDzdD~(Y(grj12rX5PNVkkJc`rRIdt^|$CERs;jG!w3+v`_XA*?@WTDV^?iG>rx=;e}zs$gG(%29&lv@@8cmVdzzOLHCpJ z0*>p{M-#dV>gp_mf*HqXqGXv*jzgtR6`gQba}o%Ng`Anx65Ewv#%C~lvK5NlW2AL= z(3H9)p0mc*YQ~&+okLxyJS+8sHkXoY&)}*?)a*iO?V@DXF2*0#LChrbYNUpCQa*k= zR)@Zl8C|~>`xR0e^PVt&m?kUzeZpzb=2&;kRnsML>MsQ;;g&Q;gQV|JPiXC%ou6ZL(agJkrl3#t89d!t_)`ALO8fpV|(_Y121(*Hz-KRf*C)@i@P YBl7UT9n=pnqXJL?ipeixz9Znj01OK8^#A|> diff --git a/loader/spring-boot-loader-classic/src/test/resources/more-jars/app.jar b/loader/spring-boot-loader-classic/src/test/resources/more-jars/app.jar deleted file mode 100644 index 3945fd020d3455db0bef268d12ae67809c90c2b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmWIWW@Zs#;Nak3u<&^2#()Gk8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gH`sg4aoHjkb7SsI^Urdhzdio^ zR`vknEVEB5L;_vcHn>WcnpjM;m?rtDT`S~ShvMy9d($@feQ_|FVx=`jeDVVQ$J!Ua zb8ci>=+x%+dYiG(_d{P@ElR4@!X9pzEAaL*yT6;;%Yri-ol5=vllBb!Q;M%t z%6zY0!Y;$w7s{R;k-lP%_xkRnoCy*|Nz%7y(GX8RxH={&pGt!ir!ua zbv6~VHa?z?gDQbi6OV5ZrSyRTh7ejpQ?{1NU2WdP(T0c8LLcm!mEwP0iiWQT(y j4>>$Qk%s_Tz@We|0~D13-mGjOWvoDG4onS(%pe{B`N?6@ diff --git a/loader/spring-boot-loader-classic/src/test/resources/nested-jars/app.jar b/loader/spring-boot-loader-classic/src/test/resources/nested-jars/app.jar deleted file mode 100644 index 5600ed279efb99189791ed00b3bbb4203efaa208..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3313 zcmc&$&u`pB6n@^_WV6nuNq;15Xxh2~q)k!0O}QYpMNPK}O_Q`y6M{s6%GsR+x4Rz8 z_NFZmp^5|Q2_a4hlmkM5I3U3R5E5MA)b@n<11fPqNN_+1aX}T|vmM*JF%=R*cr~_n z{NBv_@qO>j!fZCD67>*`oW6OM5?6t8G(WX8IX-i4x_s{z(L$T(z|Qwary$N_4@M+9 zKY4CudTMb=pP!yLEOS-3-uSX`TvJ+)YZLm>KDFuy!xQ!KQ`dRoG5zSJiDMI5>AYjq z8bYf|$Ci%aL4K@H?Nj4z4`1CpoiGsg5gE2!jwK$1#LuH2epJd3@#qUBrlr(t4a0SB z-TU%u%sonnRqCc)1=>w}dT3CkA=;a#eR&$r(*p{1A2%)2dqSaXX>3`cTvgVELj7~5 zCC;^)E5cbaRvMVeIxS0~q0+^<%f`AY{8pH8E|nY!4g3VhRmk zXjz`w6w9V-Vx39LlAiCdtI&}->8zDqn-^JijHb9MohxP66ONGHbz79HvS~|8SYD-w zv=#ESzepo=K%pJjK!ZURG<99i)4?JgB2A$@2MHe)8i>t2zj9gBJnWaY-HMbRZ`3rb zIy((}E#b83FJ6aDO@u%=_%Ij*z?9_$815kk!e~|$+U;-HJ`ID|JFkJz4VKa(M~A5k z>46|}mBAB!;trhB0X!8vyN-NL$|rbHs26t@#w6XKB9b16J`7c0fC`^dW>@wz%EjWU z|DnfUH$yuhl%aka2p8I!#5#lSi=Q?x3}XWxMF@rQ^s{LjwJXM&&@fj~O{B*0G=x;| z$e_+4)whN8zAACjSusqydNO)f?bb@etZ6>7HO@ZvZ-^$a;etjT+0$N93n$O7BVA@) zsD)5>U7B?UO(}paaHAQ{nmYz*EpY|e)}gG2xhsvf!)WM8>zNP8PLWrCy@zlf)V><3mpDW*vIK&?!hVC3;379J*H&9y$nQp!47^BG53(h zPVTXIz`Yz+?egn$4uU%{`tdD812HCU0~1ev@N463eBd#N(HXrq3i8KFJ1G)@4iO#V z-sOT@(|q_d3PSu}d@h8)_kts@x>^+d1Rp{!Ao4L661mHEN!8OyT}*w=!CutVwItTx&T( zpR)MOGl{usIOkX86VIxKZR^ugqTqMi@x+a4uW$&@%{p*nb$!8 diff --git a/loader/spring-boot-loader-classic/src/test/resources/nested-jars/nested-jar-app.jar b/loader/spring-boot-loader-classic/src/test/resources/nested-jars/nested-jar-app.jar deleted file mode 100644 index 4c2254f6352b5443de1396397423068a792b2eb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1408 zcmWIWW@h1H0D(E)T7h5&l;C8LVeoYgan$wnbJGtE;bdSw{x~uogi9;985mio! z6LHmP3{$f|6@4sX1Um@gryeYRa`N{NLHKDo&;=lj#ZM3;`q7LK1o{bXL~>4IadB!f zBzV>VjaiS+s1`J%#IPBalA4gB1ED*-xG)QotshQ_&__&tA9vBl3fLI-;d5#4I zIho0cC7JnodSEw$W1wL2!5|PuvmiGh)|<&u#CGxO#jmF=%bdT=mgSl=_g*PSmy69I z30Iu`A6uO`@#fs7b%FM)<+!R|uzy*+Xt#8#6LYcsIos#w_J02O^Bdm-hFs}N9nnD7 zwGFA#r6v|(7GaW~+O+~c3OaAwTzxoU?u>;g8s9?{^q2i}aJ!zgfOBKbLZ&vi=j+VH zz8|`(B(d|aMbN<&vpL>gX7_K~Xmqe8x%bn)nQrrJf;)E<$+SgPDrvq~FJhNr?JH%M zzLBCa*L$t+?`tkqCoYz3Xcb%8cFFvmyr$#k?fUG=FOTwUmHc9TNM_@Ii_WF~+_N+< zzP|rh`*GaGH-@Xy zJS73l79b$N@YWGTBjqqwNDf1b4Ty2L@)yK7VB9k-X$0juxN#`C4QMhbw_!CIS4Kg0 z=oO&J@H8J90yGY5mO=O%Gt(d&Ck}Kk+&DxQ0vZd-LU>$>nSqdvO~Pg@G&=#!#+sEt qPR5m)U}l5Db`wssv1BcvWmvLRfHx}}FdP}!fKY;ofgy+)!~+16@N`rF diff --git a/loader/spring-boot-loader-classic/src/test/resources/org/springframework/boot/loader/classpath-index-file.idx b/loader/spring-boot-loader-classic/src/test/resources/org/springframework/boot/loader/classpath-index-file.idx deleted file mode 100644 index b84b99a6b47..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/org/springframework/boot/loader/classpath-index-file.idx +++ /dev/null @@ -1,5 +0,0 @@ -- "BOOT-INF/layers/one/lib/a.jar" -- "BOOT-INF/layers/one/lib/b.jar" -- "BOOT-INF/layers/one/lib/c.jar" -- "BOOT-INF/layers/two/lib/d.jar" -- "BOOT-INF/layers/two/lib/e.jar" diff --git a/loader/spring-boot-loader-classic/src/test/resources/placeholders/META-INF/MANIFEST.MF b/loader/spring-boot-loader-classic/src/test/resources/placeholders/META-INF/MANIFEST.MF deleted file mode 100644 index d95a13c5284..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/placeholders/META-INF/MANIFEST.MF +++ /dev/null @@ -1,2 +0,0 @@ -Manifest-Version: 1.0 -Start-Class: ${foo.main} diff --git a/loader/spring-boot-loader-classic/src/test/resources/placeholders/loader.properties b/loader/spring-boot-loader-classic/src/test/resources/placeholders/loader.properties deleted file mode 100644 index 32f7d00f2d0..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/placeholders/loader.properties +++ /dev/null @@ -1 +0,0 @@ -foo.main: demo.FooApplication diff --git a/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/MANIFEST.MF b/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/MANIFEST.MF deleted file mode 100644 index 8b137891791..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/MANIFEST.MF +++ /dev/null @@ -1 +0,0 @@ - diff --git a/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/spring/application.xml b/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/spring/application.xml deleted file mode 100644 index cf04aa4fbe4..00000000000 --- a/loader/spring-boot-loader-classic/src/test/resources/root/META-INF/spring/application.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/loader/spring-boot-loader-tools/build.gradle b/loader/spring-boot-loader-tools/build.gradle index 03b0bf63f48..159cf0502ac 100644 --- a/loader/spring-boot-loader-tools/build.gradle +++ b/loader/spring-boot-loader-tools/build.gradle @@ -28,10 +28,6 @@ configurations { extendsFrom dependencyManagement transitive = false } - loaderClassic { - extendsFrom dependencyManagement - transitive = false - } jarmode { extendsFrom dependencyManagement transitive = false @@ -45,7 +41,6 @@ dependencies { compileOnly("ch.qos.logback:logback-classic") loader(project(":loader:spring-boot-loader")) - loaderClassic(project(":loader:spring-boot-loader-classic")) jarmode(project(":loader:spring-boot-jarmode-tools")) @@ -70,21 +65,6 @@ tasks.register("reproducibleLoaderJar", Jar) { destinationDirectory = file(generatedResources.map {it.dir("META-INF/loader") }) } -tasks.register("reproducibleLoaderClassicJar", Jar) { - dependsOn configurations.loaderClassic - from { - zipTree(configurations.loaderClassic.incoming.files.singleFile).matching { - exclude "META-INF/LICENSE.txt" - exclude "META-INF/NOTICE.txt" - exclude "META-INF/spring-boot.properties" - } - } - reproducibleFileOrder = true - preserveFileTimestamps = false - archiveFileName = "spring-boot-loader-classic.jar" - destinationDirectory = file(generatedResources.map { it.dir("META-INF/loader") }) -} - tasks.register("toolsJar", Sync) { dependsOn configurations.jarmode from { @@ -96,7 +76,7 @@ tasks.register("toolsJar", Sync) { sourceSets { main { - output.dir(generatedResources, builtBy: [toolsJar, reproducibleLoaderJar, reproducibleLoaderClassicJar]) + output.dir(generatedResources, builtBy: [toolsJar, reproducibleLoaderJar]) } } diff --git a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java index 2e4704fb614..b3c26c29aa0 100644 --- a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java +++ b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/AbstractJarWriter.java @@ -206,13 +206,7 @@ public abstract class AbstractJarWriter implements LoaderClassesWriter { @Override public void writeLoaderClasses() throws IOException { - writeLoaderClasses(LoaderImplementation.DEFAULT); - } - - @Override - public void writeLoaderClasses(@Nullable LoaderImplementation loaderImplementation) throws IOException { - writeLoaderClasses((loaderImplementation != null) ? loaderImplementation.getJarResourceName() - : LoaderImplementation.DEFAULT.getJarResourceName()); + writeLoaderClasses("META-INF/loader/spring-boot-loader.jar"); } /** diff --git a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java index 4b1aef3cb88..1e9308fca5b 100644 --- a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java +++ b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderClassesWriter.java @@ -34,14 +34,6 @@ public interface LoaderClassesWriter { */ void writeLoaderClasses() throws IOException; - /** - * Write the default required spring-boot-loader classes to the JAR. - * @param loaderImplementation the specific implementation to write - * @throws IOException if the classes cannot be written - * @since 3.2.0 - */ - void writeLoaderClasses(LoaderImplementation loaderImplementation) throws IOException; - /** * Write custom required spring-boot-loader classes to the JAR. * @param loaderJarResourceName the name of the resource containing the loader classes diff --git a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java deleted file mode 100644 index 099a1a15b77..00000000000 --- a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/LoaderImplementation.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2012-present 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. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.loader.tools; - -/** - * Supported loader implementations. - * - * @author Phillip Webb - * @since 3.2.0 - */ -public enum LoaderImplementation { - - /** - * The default recommended loader implementation. - */ - DEFAULT("META-INF/loader/spring-boot-loader.jar"), - - /** - * The classic loader implementation as used with Spring Boot 3.1 and earlier. - */ - CLASSIC("META-INF/loader/spring-boot-loader-classic.jar"); - - private final String jarResourceName; - - LoaderImplementation(String jarResourceName) { - this.jarResourceName = jarResourceName; - } - - /** - * Return the name of the nested resource that can be loaded from the tools jar. - * @return the jar resource name - */ - public String getJarResourceName() { - return this.jarResourceName; - } - -} diff --git a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java index 0c24ccbb25e..50754fa09c9 100644 --- a/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java +++ b/loader/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java @@ -93,8 +93,6 @@ public abstract class Packager { private @Nullable Layout layout; - private @Nullable LoaderImplementation loaderImplementation; - private @Nullable LayoutFactory layoutFactory; private @Nullable Layers layers; @@ -142,14 +140,6 @@ public abstract class Packager { this.layout = layout; } - /** - * Sets the loader implementation to use. - * @param loaderImplementation the loaderImplementation to set - */ - public void setLoaderImplementation(@Nullable LoaderImplementation loaderImplementation) { - this.loaderImplementation = loaderImplementation; - } - /** * Sets the layout factory for the jar. The factory can be used when no specific * layout is specified. @@ -231,7 +221,7 @@ public abstract class Packager { customLoaderLayout.writeLoadedClasses(writer); } else if (layout.isExecutable()) { - writer.writeLoaderClasses(this.loaderImplementation); + writer.writeLoaderClasses(); } } diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 7269f5bebe5..e0a0f806b6e 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2041,8 +2041,6 @@ bom { "spring-boot-ldap", "spring-boot-liquibase", "spring-boot-loader", - "spring-boot-loader-classic", - "spring-boot-loader-tools", "spring-boot-mail", "spring-boot-micrometer-metrics", "spring-boot-micrometer-observation", diff --git a/settings.gradle b/settings.gradle index 9b13448c9f8..767a0ca918c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -267,7 +267,6 @@ include "starter:spring-boot-starter-zipkin" include "loader:spring-boot-jarmode-tools" include "loader:spring-boot-loader" -include "loader:spring-boot-loader-classic" include "loader:spring-boot-loader-tools" include "buildpack:spring-boot-buildpack-platform" @@ -378,7 +377,6 @@ include ":integration-test:spring-boot-actuator-integration-tests" include ":integration-test:spring-boot-configuration-processor-integration-tests" include ":integration-test:spring-boot-integration-tests" include ":integration-test:spring-boot-launch-script-integration-tests" -include ":integration-test:spring-boot-loader-classic-integration-tests" include ":integration-test:spring-boot-loader-integration-tests" include ":integration-test:spring-boot-server-integration-tests" include ":integration-test:spring-boot-sni-integration-tests"