8151542: URL resources for multi-release jar files have a #runtime fragment appended to them
Reviewed-by: alanb, chegar, psandoz, sherman
This commit is contained in:
parent
766b494979
commit
35e6b00ee0
@ -658,6 +658,28 @@ class JarFile extends ZipFile {
|
||||
return vze == null ? ze : vze;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the real name of a {@code JarEntry}. If this {@code JarFile} is
|
||||
* a multi-release jar file and is configured to be processed as such, the
|
||||
* name returned by this method is the path name of the versioned entry
|
||||
* that the {@code JarEntry} represents, rather than the path name of the
|
||||
* base entry that {@link JarEntry#getName()} returns. If the
|
||||
* {@code JarEntry} does not represent a versioned entry, or the
|
||||
* jar file is not a multi-release jar file or {@code JarFile} is not
|
||||
* configured for processing a multi-release jar file, this method returns
|
||||
* the same name that {@link JarEntry#getName()} returns.
|
||||
*
|
||||
* @param entry the JarEntry
|
||||
* @return the real name of the JarEntry
|
||||
* @since 9
|
||||
*/
|
||||
String getRealName(JarEntry entry) {
|
||||
if (entry instanceof JarFileEntry) {
|
||||
return ((JarFileEntry)entry).realName();
|
||||
}
|
||||
return entry.getName();
|
||||
}
|
||||
|
||||
private class JarFileEntry extends JarEntry {
|
||||
final private String name;
|
||||
|
||||
@ -684,7 +706,7 @@ class JarFile extends ZipFile {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (certs == null && jv != null) {
|
||||
certs = jv.getCerts(JarFile.this, reifiedEntry());
|
||||
certs = jv.getCerts(JarFile.this, realEntry());
|
||||
}
|
||||
return certs == null ? null : certs.clone();
|
||||
}
|
||||
@ -695,17 +717,20 @@ class JarFile extends ZipFile {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (signers == null && jv != null) {
|
||||
signers = jv.getCodeSigners(JarFile.this, reifiedEntry());
|
||||
signers = jv.getCodeSigners(JarFile.this, realEntry());
|
||||
}
|
||||
return signers == null ? null : signers.clone();
|
||||
}
|
||||
JarFileEntry reifiedEntry() {
|
||||
JarFileEntry realEntry() {
|
||||
if (isMultiRelease()) {
|
||||
String entryName = super.getName();
|
||||
return entryName.equals(this.name) ? this : new JarFileEntry(entryName, this);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
String realName() {
|
||||
return super.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -876,11 +901,11 @@ class JarFile extends ZipFile {
|
||||
private JarEntry verifiableEntry(ZipEntry ze) {
|
||||
if (ze instanceof JarFileEntry) {
|
||||
// assure the name and entry match for verification
|
||||
return ((JarFileEntry)ze).reifiedEntry();
|
||||
return ((JarFileEntry)ze).realEntry();
|
||||
}
|
||||
ze = getJarEntry(ze.getName());
|
||||
if (ze instanceof JarFileEntry) {
|
||||
return ((JarFileEntry)ze).reifiedEntry();
|
||||
return ((JarFileEntry)ze).realEntry();
|
||||
}
|
||||
return (JarEntry)ze;
|
||||
}
|
||||
|
@ -60,4 +60,8 @@ class JavaUtilJarAccessImpl implements JavaUtilJarAccess {
|
||||
public List<Object> getManifestDigests(JarFile jar) {
|
||||
return jar.getManifestDigests();
|
||||
}
|
||||
|
||||
public String getRealName(JarFile jar, JarEntry entry) {
|
||||
return jar.getRealName(entry);
|
||||
}
|
||||
}
|
||||
|
@ -372,9 +372,15 @@ public class URLClassPath {
|
||||
return java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedExceptionAction<>() {
|
||||
public Loader run() throws IOException {
|
||||
String protocol = url.getProtocol(); // lower cased in URL
|
||||
String file = url.getFile();
|
||||
if (file != null && file.endsWith("/")) {
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
if ("jar".equals(protocol)
|
||||
&& file != null && (file.indexOf("!/") == file.length() - 2)) {
|
||||
// extract the nested URL
|
||||
URL nestedUrl = new URL(file.substring(0, file.length() - 2));
|
||||
return new JarLoader(nestedUrl, jarHandler, lmap);
|
||||
} else if (file != null && file.endsWith("/")) {
|
||||
if ("file".equals(protocol)) {
|
||||
return new FileLoader(url);
|
||||
} else {
|
||||
return new Loader(url);
|
||||
@ -718,13 +724,13 @@ public class URLClassPath {
|
||||
|
||||
final URL url;
|
||||
try {
|
||||
String nm;
|
||||
if (jar.isMultiRelease()) {
|
||||
// add #runtime fragment to tell JarURLConnection to use
|
||||
// runtime versioning if the underlying jar file is multi-release
|
||||
url = new URL(getBaseURL(), ParseUtil.encodePath(name, false) + "#runtime");
|
||||
nm = SharedSecrets.javaUtilJarAccess().getRealName(jar, entry);
|
||||
} else {
|
||||
url = new URL(getBaseURL(), ParseUtil.encodePath(name, false));
|
||||
nm = name;
|
||||
}
|
||||
url = new URL(getBaseURL(), ParseUtil.encodePath(nm, false));
|
||||
if (check) {
|
||||
URLClassPath.check(url);
|
||||
}
|
||||
@ -940,7 +946,8 @@ public class URLClassPath {
|
||||
|
||||
ensureOpen();
|
||||
|
||||
if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) { // Only get manifest when necessary
|
||||
// Only get manifest when necessary
|
||||
if (SharedSecrets.javaUtilJarAccess().jarFileHasClassPathAttribute(jar)) {
|
||||
Manifest man = jar.getManifest();
|
||||
if (man != null) {
|
||||
Attributes attr = man.getMainAttributes();
|
||||
|
@ -41,4 +41,5 @@ public interface JavaUtilJarAccess {
|
||||
public Enumeration<JarEntry> entries2(JarFile jar);
|
||||
public void setEagerValidation(JarFile jar, boolean eager);
|
||||
public List<Object> getManifestDigests(JarFile jar);
|
||||
public String getRealName(JarFile jar, JarEntry entry);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
* @bug 8132734
|
||||
* @summary Test the System properties for JarFile that support multi-release jar files
|
||||
* @library /lib/testlibrary/java/util/jar
|
||||
* @build Compiler JarBuilder CreateMultiReleaseTestJars
|
||||
* @build Compiler JarBuilder CreateMultiReleaseTestJars SimpleHttpServer
|
||||
* @run testng MultiReleaseJarHttpProperties
|
||||
* @run testng/othervm -Djdk.util.jar.version=0 MultiReleaseJarHttpProperties
|
||||
* @run testng/othervm -Djdk.util.jar.version=8 MultiReleaseJarHttpProperties
|
||||
@ -43,8 +43,6 @@
|
||||
* @run testng/othervm -Djdk.util.jar.enableMultiRelease=force MultiReleaseJarHttpProperties
|
||||
*/
|
||||
|
||||
import com.sun.net.httpserver.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -73,7 +71,7 @@ public class MultiReleaseJarHttpProperties extends MultiReleaseJarProperties {
|
||||
@Override
|
||||
protected void initializeClassLoader() throws Exception {
|
||||
URL[] urls = new URL[]{
|
||||
new URL("http://localhost:" + server.getPort() + "/multi-release-jar")
|
||||
new URL("http://localhost:" + server.getPort() + "/multi-release.jar")
|
||||
};
|
||||
cldr = new URLClassLoader(urls);
|
||||
// load any class, Main is convenient and in the root entries
|
||||
@ -112,45 +110,3 @@ public class MultiReleaseJarHttpProperties extends MultiReleaseJarProperties {
|
||||
getResource(rootClass, resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extremely simple server that only performs one task. The server listens for
|
||||
* requests on the ephemeral port. If it sees a request that begins with
|
||||
* "/multi-release-jar", it consumes the request and returns a stream of bytes
|
||||
* representing the jar file multi-release.jar found in "userdir".
|
||||
*/
|
||||
class SimpleHttpServer {
|
||||
private static final String userdir = System.getProperty("user.dir", ".");
|
||||
private static final Path multirelease = Paths.get(userdir, "multi-release.jar");
|
||||
|
||||
private final HttpServer server;
|
||||
|
||||
public SimpleHttpServer() throws IOException {
|
||||
server = HttpServer.create();
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
server.bind(new InetSocketAddress(0), 0);
|
||||
server.createContext("/multi-release-jar", t -> {
|
||||
try (InputStream is = t.getRequestBody()) {
|
||||
is.readAllBytes(); // probably not necessary to consume request
|
||||
byte[] bytes = Files.readAllBytes(multirelease);
|
||||
t.sendResponseHeaders(200, bytes.length);
|
||||
try (OutputStream os = t.getResponseBody()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
}
|
||||
});
|
||||
server.setExecutor(null); // creates a default executor
|
||||
server.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
server.stop(0);
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return server.getAddress().getPort();
|
||||
}
|
||||
}
|
||||
|
||||
|
74
jdk/test/lib/testlibrary/java/util/jar/SimpleHttpServer.java
Normal file
74
jdk/test/lib/testlibrary/java/util/jar/SimpleHttpServer.java
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (c) 2015, 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 com.sun.net.httpserver.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Extremely simple server that only performs one task. The server listens for
|
||||
* requests on the ephemeral port. If it sees a request that begins with
|
||||
* "/multi-release.jar", it consumes the request and returns a stream of bytes
|
||||
* representing the jar file multi-release.jar found in "userdir".
|
||||
*/
|
||||
class SimpleHttpServer {
|
||||
private static final String userdir = System.getProperty("user.dir", ".");
|
||||
private static final Path multirelease = Paths.get(userdir, "multi-release.jar");
|
||||
|
||||
private final HttpServer server;
|
||||
|
||||
public SimpleHttpServer() throws IOException {
|
||||
server = HttpServer.create();
|
||||
}
|
||||
|
||||
public void start() throws IOException {
|
||||
server.bind(new InetSocketAddress(0), 0);
|
||||
server.createContext("/multi-release.jar", t -> {
|
||||
try (InputStream is = t.getRequestBody()) {
|
||||
is.readAllBytes(); // probably not necessary to consume request
|
||||
byte[] bytes = Files.readAllBytes(multirelease);
|
||||
t.sendResponseHeaders(200, bytes.length);
|
||||
try (OutputStream os = t.getResponseBody()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
}
|
||||
});
|
||||
server.setExecutor(null); // creates a default executor
|
||||
server.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
server.stop(0);
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return server.getAddress().getPort();
|
||||
}
|
||||
}
|
||||
|
@ -26,19 +26,25 @@
|
||||
* @bug 8132734
|
||||
* @summary Test that URL connections to multi-release jars can be runtime versioned
|
||||
* @library /lib/testlibrary/java/util/jar
|
||||
* @build Compiler JarBuilder CreateMultiReleaseTestJars
|
||||
* @build Compiler JarBuilder CreateMultiReleaseTestJars SimpleHttpServer
|
||||
* @run testng MultiReleaseJarURLConnection
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import jdk.Version;
|
||||
|
||||
import org.testng.Assert;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
@ -47,46 +53,78 @@ import org.testng.annotations.Test;
|
||||
|
||||
public class MultiReleaseJarURLConnection {
|
||||
String userdir = System.getProperty("user.dir",".");
|
||||
String file = userdir + "/signed-multi-release.jar";
|
||||
String unversioned = userdir + "/unversioned.jar";
|
||||
String unsigned = userdir + "/multi-release.jar";
|
||||
String signed = userdir + "/signed-multi-release.jar";
|
||||
SimpleHttpServer server;
|
||||
|
||||
@BeforeClass
|
||||
public void initialize() throws Exception {
|
||||
CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars();
|
||||
creator.compileEntries();
|
||||
creator.buildUnversionedJar();
|
||||
creator.buildMultiReleaseJar();
|
||||
creator.buildSignedMultiReleaseJar();
|
||||
|
||||
server = new SimpleHttpServer();
|
||||
server.start();
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public void close() throws IOException {
|
||||
Files.delete(Paths.get(userdir, "multi-release.jar"));
|
||||
Files.delete(Paths.get(userdir, "signed-multi-release.jar"));
|
||||
// Windows requires server to stop before file is deleted
|
||||
if (server != null)
|
||||
server.stop();
|
||||
Files.delete(Paths.get(unversioned));
|
||||
Files.delete(Paths.get(unsigned));
|
||||
Files.delete(Paths.get(signed));
|
||||
}
|
||||
|
||||
@DataProvider(name = "data")
|
||||
public Object[][] createData() {
|
||||
return new Object[][]{
|
||||
{"unsigned file", userdir + "/multi-release.jar"},
|
||||
{"signed file", userdir + "/signed-multi-release.jar"},
|
||||
{"unversioned", unversioned},
|
||||
{"unsigned", unsigned},
|
||||
{"signed", signed}
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "data")
|
||||
public void testRuntimeVersioning(String ignore, String file) throws Exception {
|
||||
public void testRuntimeVersioning(String style, String file) throws Exception {
|
||||
String urlFile = "jar:file:" + file + "!/";
|
||||
String urlEntry = urlFile + "version/Version.java";
|
||||
String baseUrlEntry = urlFile + "version/Version.java";
|
||||
String rtreturn = "return " + Version.current().major();
|
||||
|
||||
Assert.assertTrue(readAndCompare(new URL(urlEntry), "return 8"));
|
||||
// #runtime is "magic"
|
||||
Assert.assertTrue(readAndCompare(new URL(urlEntry + "#runtime"), "return 9"));
|
||||
Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
|
||||
// #runtime is "magic" for a multi-release jar, but not for unversioned jar
|
||||
Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"),
|
||||
style.equals("unversioned") ? "return 8" : rtreturn));
|
||||
// #fragment or any other fragment is not magic
|
||||
Assert.assertTrue(readAndCompare(new URL(urlEntry + "#fragment"), "return 8"));
|
||||
Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8"));
|
||||
// cached entities not affected
|
||||
Assert.assertTrue(readAndCompare(new URL(urlEntry), "return 8"));
|
||||
Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
|
||||
|
||||
// the following tests will not work with unversioned jars
|
||||
if (style.equals("unversioned")) return;
|
||||
|
||||
// direct access to versioned entry
|
||||
String versUrlEntry = urlFile + "META-INF/versions/" + Version.current().major()
|
||||
+ "/version/Version.java";
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn));
|
||||
// adding any fragment does not change things
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn));
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn));
|
||||
|
||||
// it really doesn't change things
|
||||
versUrlEntry = urlFile + "META-INF/versions/10/version/Version.java";
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry), "return 10"));
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), "return 10"));
|
||||
Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), "return 10"));
|
||||
}
|
||||
|
||||
@Test(dataProvider = "data")
|
||||
public void testCachedJars(String ignore, String file) throws Exception {
|
||||
public void testCachedJars(String style, String file) throws Exception {
|
||||
String urlFile = "jar:file:" + file + "!/";
|
||||
|
||||
URL rootUrl = new URL(urlFile);
|
||||
@ -98,7 +136,11 @@ public class MultiReleaseJarURLConnection {
|
||||
juc = (JarURLConnection)runtimeUrl.openConnection();
|
||||
JarFile runtimeJar = juc.getJarFile();
|
||||
JarFile.Release runtime = runtimeJar.getVersion();
|
||||
Assert.assertNotEquals(root, runtime);
|
||||
if (style.equals("unversioned")) {
|
||||
Assert.assertEquals(root, runtime);
|
||||
} else {
|
||||
Assert.assertNotEquals(root, runtime);
|
||||
}
|
||||
|
||||
juc = (JarURLConnection)rootUrl.openConnection();
|
||||
JarFile jar = juc.getJarFile();
|
||||
@ -115,6 +157,63 @@ public class MultiReleaseJarURLConnection {
|
||||
jar.close(); // probably not needed
|
||||
}
|
||||
|
||||
@DataProvider(name = "resourcedata")
|
||||
public Object[][] createResourceData() throws Exception {
|
||||
return new Object[][]{
|
||||
{"unversioned", Paths.get(unversioned).toUri().toURL()},
|
||||
{"unsigned", Paths.get(unsigned).toUri().toURL()},
|
||||
{"signed", Paths.get(signed).toUri().toURL()},
|
||||
{"unversioned", new URL("file:" + unversioned)},
|
||||
{"unsigned", new URL("file:" + unsigned)},
|
||||
{"signed", new URL("file:" + signed)},
|
||||
{"unversioned", new URL("jar:file:" + unversioned + "!/")},
|
||||
{"unsigned", new URL("jar:file:" + unsigned + "!/")},
|
||||
{"signed", new URL("jar:file:" + signed + "!/")},
|
||||
// external jar received via http protocol
|
||||
{"http", new URL("jar:http://localhost:" + server.getPort() + "/multi-release.jar!/")},
|
||||
{"http", new URL("http://localhost:" + server.getPort() + "/multi-release.jar")},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "resourcedata")
|
||||
public void testResources(String style, URL url) throws Throwable {
|
||||
//System.out.println(" testing " + style + " url: " + url);
|
||||
URL[] urls = {url};
|
||||
URLClassLoader cldr = new URLClassLoader(urls);
|
||||
Class<?> vcls = cldr.loadClass("version.Version");
|
||||
|
||||
// verify we are loading a runtime versioned class
|
||||
MethodType mt = MethodType.methodType(int.class);
|
||||
MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt);
|
||||
Assert.assertEquals((int)mh.invoke(vcls.newInstance()),
|
||||
style.equals("unversioned") ? 8 : Version.current().major());
|
||||
|
||||
// now get a resource and verify that we don't have a fragment attached
|
||||
URL vclsUrl = vcls.getResource("/version/Version.class");
|
||||
String fragment = vclsUrl.getRef();
|
||||
Assert.assertNull(fragment);
|
||||
|
||||
// and verify that the the url is a reified pointer to the runtime entry
|
||||
String rep = vclsUrl.toString();
|
||||
//System.out.println(" getResource(\"/version/Version.class\") returned: " + rep);
|
||||
if (style.equals("http")) {
|
||||
Assert.assertTrue(rep.startsWith("jar:http:"));
|
||||
} else {
|
||||
Assert.assertTrue(rep.startsWith("jar:file:"));
|
||||
}
|
||||
String suffix;
|
||||
if (style.equals("unversioned")) {
|
||||
suffix = ".jar!/version/Version.class";
|
||||
} else {
|
||||
suffix = ".jar!/META-INF/versions/" + Version.current().major()
|
||||
+ "/version/Version.class";
|
||||
}
|
||||
Assert.assertTrue(rep.endsWith(suffix));
|
||||
cldr.close();
|
||||
}
|
||||
|
||||
|
||||
private boolean readAndCompare(URL url, String match) throws Exception {
|
||||
boolean result;
|
||||
// necessary to do it this way, instead of openStream(), so we can
|
||||
|
Loading…
x
Reference in New Issue
Block a user