| /* | |
| * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| import java.io.InputStream; | |
| import java.net.URL; | |
| import java.net.URLClassLoader; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.Arrays; | |
| import java.util.jar.Attributes; | |
| import java.util.jar.Manifest; | |
| import jdk.test.lib.util.JarUtils; | |
| import org.junit.jupiter.api.BeforeAll; | |
| import org.junit.jupiter.params.ParameterizedTest; | |
| import org.junit.jupiter.params.provider.ValueSource; | |
| import static java.nio.charset.StandardCharsets.US_ASCII; | |
| import static org.junit.jupiter.api.Assertions.assertEquals; | |
| import static org.junit.jupiter.api.Assertions.assertFalse; | |
| import static org.junit.jupiter.api.Assertions.assertNotNull; | |
| import static org.junit.jupiter.api.Assertions.assertNull; | |
| /* | |
| * @test | |
| * @bug 8338445 | |
| * @summary verify that the jdk.internal.loader.URLClassPath closes the JarFile | |
| * instances that it no longer uses for loading | |
| * @library /test/lib | |
| * @build jdk.test.lib.util.JarUtils | |
| * @comment This test expects MalformedURLException for some specific URLs. | |
| * We use othervm to prevent interference from other tests which | |
| * might have installed custom URLStreamHandler(s) | |
| * @run junit/othervm JarLoaderCloseTest | |
| */ | |
| public class JarLoaderCloseTest { | |
| private static final String RESOURCE_NAME = "foo-bar.txt"; | |
| private static final String RESOURCE_CONTENT = "Hello world"; | |
| private static final Path TEST_SCRATCH_DIR = Path.of("."); | |
| @BeforeAll | |
| static void beforeAll() throws Exception { | |
| // create a file which will be added to the JAR file that gets tested | |
| Files.writeString(TEST_SCRATCH_DIR.resolve(RESOURCE_NAME), RESOURCE_CONTENT); | |
| } | |
| /* | |
| * Creates a JAR file with a manifest which has a Class-Path entry value with malformed URLs. | |
| * Then uses a URLClassLoader backed by the JAR file in its classpath, loads some resource, | |
| * closes the URLClassLoader and then expects that the underlying JAR file can be deleted | |
| * from the filesystem. | |
| */ | |
| @ParameterizedTest | |
| @ValueSource(strings = { | |
| "C:\\foo\\bar\\hello/world.jar lib2.jar", | |
| "C:/hello/world/foo.jar", | |
| "lib4.jar C:\\bar\\foo\\world/hello.jar" | |
| }) | |
| public void testMalformedClassPathEntry(final String classPathValue) throws Exception { | |
| final Manifest manifest = createManifestWithClassPath(classPathValue); | |
| final Path jar = Files.createTempFile(TEST_SCRATCH_DIR, "8338445", ".jar"); | |
| // create the JAR file with the given manifest and an arbitrary file | |
| JarUtils.createJarFile(jar, manifest, TEST_SCRATCH_DIR, Path.of(RESOURCE_NAME)); | |
| System.out.println("created jar at " + jar + " with manifest:"); | |
| manifest.write(System.out); | |
| final URL[] urlClassPath = new URL[]{jar.toUri().toURL()}; | |
| // Create a URLClassLoader backed by the JAR file and load a non-existent resource just to | |
| // exercise the URLClassPath code of loading the jar and parsing the Class-Path entry. | |
| // Then close the classloader. After the classloader is closed | |
| // issue a delete on the underlying JAR file on the filesystem. The delete is expected | |
| // to succeed. | |
| try (final URLClassLoader cl = new URLClassLoader(urlClassPath)) { | |
| try (final InputStream is = cl.getResourceAsStream("non-existent.txt")) { | |
| assertNull(is, "unexpectedly found a resource in classpath " | |
| + Arrays.toString(urlClassPath)); | |
| } | |
| } | |
| // now delete the JAR file and verify the delete worked | |
| Files.delete(jar); | |
| assertFalse(Files.exists(jar), jar + " exists even after being deleted"); | |
| } | |
| /* | |
| * Creates a JAR file with a manifest which has a Class-Path entry value with URLs | |
| * that are parsable but point to files that don't exist on the filesystem. | |
| * Then uses a URLClassLoader backed by the JAR file in its classpath, loads some resource, | |
| * closes the URLClassLoader and then expects that the underlying JAR file can be deleted | |
| * from the filesystem. | |
| */ | |
| @ParameterizedTest | |
| @ValueSource(strings = { | |
| "/home/me/hello/world.jar lib9.jar", | |
| "lib10.jar" | |
| }) | |
| public void testParsableClassPathEntry(final String classPathValue) throws Exception { | |
| final Manifest manifest = createManifestWithClassPath(classPathValue); | |
| final Path jar = Files.createTempFile(TEST_SCRATCH_DIR, "8338445", ".jar"); | |
| // create the JAR file with the given manifest and an arbitrary file | |
| JarUtils.createJarFile(jar, manifest, TEST_SCRATCH_DIR, Path.of(RESOURCE_NAME)); | |
| System.out.println("created jar at " + jar + " with manifest:"); | |
| manifest.write(System.out); | |
| final URL[] urlClassPath = new URL[]{jar.toUri().toURL()}; | |
| // Create a URLClassLoader backed by the JAR file and load a resource | |
| // and verify the resource contents. | |
| // Then close the classloader. After the classloader is closed | |
| // issue a delete on the underlying JAR file on the filesystem. The delete is expected | |
| // to succeed. | |
| try (final URLClassLoader cl = new URLClassLoader(urlClassPath)) { | |
| try (final InputStream is = cl.getResourceAsStream(RESOURCE_NAME)) { | |
| assertNotNull(is, RESOURCE_NAME + " not located by classloader in classpath " | |
| + Arrays.toString(urlClassPath)); | |
| final String content = new String(is.readAllBytes(), US_ASCII); | |
| assertEquals(RESOURCE_CONTENT, content, "unexpected content in " + RESOURCE_NAME); | |
| } | |
| } | |
| // now delete the JAR file and verify the delete worked | |
| Files.delete(jar); | |
| assertFalse(Files.exists(jar), jar + " exists even after being deleted"); | |
| } | |
| private static Manifest createManifestWithClassPath(final String classPathValue) { | |
| final Manifest manifest = new Manifest(); | |
| final Attributes mainAttributes = manifest.getMainAttributes(); | |
| mainAttributes.putValue("Manifest-Version", "1.0"); | |
| mainAttributes.putValue("Class-Path", classPathValue); | |
| return manifest; | |
| } | |
| } |