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:
Steve Drach 2016-05-02 09:03:38 -07:00 committed by Steve Drach
parent 766b494979
commit 35e6b00ee0
7 changed files with 239 additions and 73 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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