Rename spring-boot-loader to spring-boot-loader-classic
Rename the `spring-boot-loader` module to `spring-boot-loader-classic` so that we can introduce an alternative loader implementation. See gh-37669
This commit is contained in:
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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<String> extraLibs)
|
||||
throws IOException {
|
||||
return createJarArchive(name, null, entryPrefix, indexed, extraLibs);
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
protected File createJarArchive(String name, Manifest manifest, String entryPrefix, boolean indexed,
|
||||
List<String> 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<JarEntry> 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<URL> getUrls(List<Archive> archives) throws MalformedURLException {
|
||||
Set<URL> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<URL> urls = indexFile.getUrls();
|
||||
List<File> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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<Archive> 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<Archive> 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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jarFilesPresentInBootInfLibsAndNotInClasspathIndexShouldBeAddedAfterBootInfClasses() throws Exception {
|
||||
ArrayList<String> 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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
List<File> 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<Archive> 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<File> getExpectedFiles(File parent) {
|
||||
List<File> 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<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
List<File> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<File, JarFile> rootFileCache = ((SoftReference<Map<File, JarFile>>) 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<Archive> 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<Archive> 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<Archive> 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<Archive> 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<Archive> 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<Archive> archives() throws Exception {
|
||||
List<Archive> 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<String>) 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<Archive> 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<Archive> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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<Archive> 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<Archive> 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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
assertThat(urls).containsExactly(getExpectedFileUrls(explodedRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
void warFilesPresentInWebInfLibsAndNotInClasspathIndexShouldBeAddedAfterWebInfClasses() throws Exception {
|
||||
ArrayList<String> 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<Archive> archives = launcher.getClassPathArchivesIterator();
|
||||
URLClassLoader classLoader = (URLClassLoader) launcher.createClassLoader(archives);
|
||||
URL[] urls = classLoader.getURLs();
|
||||
List<File> 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<File> getExpectedFiles(File parent) {
|
||||
List<File> 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<File> getExpectedFilesWithExtraLibs(File parent) {
|
||||
List<File> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<JarEntry> 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<String, Archive.Entry> 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<String, Entry> 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<String, Archive.Entry> 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<String, Archive.Entry> 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<String, Archive.Entry> 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<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<>();
|
||||
for (Archive.Entry entry : archive) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<String, Archive.Entry> 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<Entry> 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<JarEntry> 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<String, Archive.Entry> getEntriesMap(Archive archive) {
|
||||
Map<String, Archive.Entry> entries = new HashMap<>();
|
||||
for (Archive.Entry entry : archive) {
|
||||
entries.put(entry.getName(), entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<Future<Boolean>> 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<Boolean> future : results) {
|
||||
assertThat(future.get()).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<String> 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<CentralDirectoryFileHeader> 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<CentralDirectoryFileHeader> 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<CentralDirectoryFileHeader> getHeaders() {
|
||||
return this.headers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MockCentralDirectoryVisitor implements CentralDirectoryVisitor {
|
||||
|
||||
private final List<String> 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<String> getInvocations() {
|
||||
return this.invocations;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,736 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<java.util.jar.JarEntry> 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<java.util.jar.JarEntry> 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<java.util.jar.JarEntry> 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<java.util.jar.JarEntry> 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<JarEntry> 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<JarEntry> 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() > 6 * 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<JarEntry> 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<java.util.jar.JarEntry> entries = jar.entries();
|
||||
JarEntry entry = entries.nextElement();
|
||||
assertThat(entry.getLastModifiedTime().toInstant()).isEqualTo(Instant.EPOCH);
|
||||
assertThat(entry.getName()).isEqualTo("1.dat");
|
||||
}
|
||||
}
|
||||
|
||||
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<JarEntry> iterator = this.jarFile.iterator();
|
||||
List<String> 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<JarEntry> iterator = this.jarFile.iterator();
|
||||
iterator.next();
|
||||
this.jarFile.close();
|
||||
assertThatZipFileClosedIsThrownBy(() -> iterator.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stream() {
|
||||
Stream<String> 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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<Call> 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<java.util.jar.JarEntry> entries() {
|
||||
mark(Call.ENTRIES);
|
||||
return super.entries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<java.util.jar.JarEntry> 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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<String> 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('\\', '/');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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<File, JarFile> rootFileCache = ((SoftReference<Map<File, JarFile>>) ReflectionTestUtils
|
||||
.getField(Handler.class, "rootFileCache")).get();
|
||||
if (rootFileCache != null) {
|
||||
for (JarFile rootJarFile : rootFileCache.values()) {
|
||||
rootJarFile.close();
|
||||
}
|
||||
rootFileCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWhenNotEmptyShouldReturnFalse() {
|
||||
assertThat(new StringSequence("x").isEmpty()).isFalse();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +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 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]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchWhenJarModePropertyIsNotAcceptedThrowsException(CapturedOutput out) throws Exception {
|
||||
System.setProperty("jarmode", "idontexist");
|
||||
new TestLauncher().launch(new String[] { "boot" });
|
||||
assertThat(out).contains("Unsupported jarmode 'idontexist'");
|
||||
}
|
||||
|
||||
private static class TestLauncher extends Launcher {
|
||||
|
||||
@Override
|
||||
protected String getMainClass() throws Exception {
|
||||
throw new IllegalStateException("Should not be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<Archive> getClassPathArchivesIterator() throws Exception {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void launch(String[] args) throws Exception {
|
||||
super.launch(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
loader.main: demo.Application
|
||||
@@ -1 +0,0 @@
|
||||
loader.main: my.BootInfBarApplication
|
||||
@@ -1,3 +0,0 @@
|
||||
foo: Application
|
||||
loader.main: my.${foo}
|
||||
loader.path: etc
|
||||
@@ -1 +0,0 @@
|
||||
loader.main: demo.Application
|
||||
@@ -1,3 +0,0 @@
|
||||
# Jar Modes
|
||||
org.springframework.boot.loader.jarmode.JarMode=\
|
||||
org.springframework.boot.loader.jarmode.TestJarMode
|
||||
@@ -1 +0,0 @@
|
||||
loader.main: my.BarApplication
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
loader.main: demo.HomeApplication
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"
|
||||
@@ -1,2 +0,0 @@
|
||||
Manifest-Version: 1.0
|
||||
Start-Class: ${foo.main}
|
||||
@@ -1 +0,0 @@
|
||||
foo.main: demo.FooApplication
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user