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:
Phillip Webb
2023-09-18 13:41:31 -07:00
parent c22548982a
commit aeb6537f57
105 changed files with 67 additions and 67 deletions

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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
}
}
}

View File

@@ -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('\\', '/');
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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"));
}
}

View File

@@ -1,3 +0,0 @@
foo: Application
loader.main: my.${foo}
loader.path: etc

View File

@@ -1,3 +0,0 @@
# Jar Modes
org.springframework.boot.loader.jarmode.JarMode=\
org.springframework.boot.loader.jarmode.TestJarMode

View File

@@ -1 +0,0 @@
loader.main: my.BarApplication

View File

@@ -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 {
}

View File

@@ -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"

View File

@@ -1,2 +0,0 @@
Manifest-Version: 1.0
Start-Class: ${foo.main}

View File

@@ -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>