1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8132734 8144062 8159785
  27  * @summary Test that URL connections to multi-release jars can be runtime versioned
  28  * @library /lib/testlibrary/java/util/jar
  29  * @modules java.compiler
  30  *          jdk.compiler
  31  *          jdk.httpserver
  32  *          jdk.jartool
  33  * @build Compiler JarBuilder CreateMultiReleaseTestJars SimpleHttpServer
  34  * @run testng MultiReleaseJarURLConnection
  35  */
  36 
  37 import java.io.IOException;
  38 import java.io.InputStream;
  39 import java.lang.invoke.MethodHandle;
  40 import java.lang.invoke.MethodHandles;
  41 import java.lang.invoke.MethodType;
  42 import java.net.JarURLConnection;
  43 import java.net.URL;
  44 import java.net.URLClassLoader;
  45 import java.net.URLConnection;
  46 import java.nio.file.Files;
  47 import java.nio.file.Paths;
  48 import java.util.Enumeration;
  49 import java.util.jar.JarFile;
  50 
  51 import org.testng.Assert;
  52 import org.testng.annotations.AfterClass;
  53 import org.testng.annotations.BeforeClass;
  54 import org.testng.annotations.DataProvider;
  55 import org.testng.annotations.Test;
  56 
  57 public class MultiReleaseJarURLConnection {
  58     String userdir = System.getProperty("user.dir",".");
  59     String unversioned = userdir + "/unversioned.jar";
  60     String unsigned = userdir + "/multi-release.jar";
  61     String signed = userdir + "/signed-multi-release.jar";
  62     SimpleHttpServer server;
  63 
  64     @BeforeClass
  65     public void initialize() throws Exception {
  66         CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars();
  67         creator.compileEntries();
  68         creator.buildUnversionedJar();
  69         creator.buildMultiReleaseJar();
  70         creator.buildSignedMultiReleaseJar();
  71 
  72         server = new SimpleHttpServer();
  73         server.start();
  74 
  75     }
  76 
  77     @AfterClass
  78     public void close() throws IOException {
  79         // Windows requires server to stop before file is deleted
  80         if (server != null)
  81             server.stop();
  82         Files.delete(Paths.get(unversioned));
  83         Files.delete(Paths.get(unsigned));
  84         Files.delete(Paths.get(signed));
  85     }
  86 
  87     @DataProvider(name = "data")
  88     public Object[][] createData() {
  89         return new Object[][]{
  90                 {"unversioned", unversioned},
  91                 {"unsigned", unsigned},
  92                 {"signed", signed}
  93         };
  94     }
  95 
  96     @Test(dataProvider = "data")
  97     public void testRuntimeVersioning(String style, String file) throws Exception {
  98         String urlFile = "jar:file:" + file + "!/";
  99         String baseUrlEntry = urlFile + "version/Version.java";
 100         String rtreturn = "return " + Runtime.version().major();
 101 
 102         Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
 103         // #runtime is "magic" for a multi-release jar, but not for unversioned jar
 104         Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"),
 105                 style.equals("unversioned") ? "return 8" : rtreturn));
 106         // #fragment or any other fragment is not magic
 107         Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8"));
 108         // cached entities not affected
 109         Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
 110 
 111         // the following tests will not work with unversioned jars
 112         if (style.equals("unversioned")) return;
 113 
 114         // direct access to versioned entry
 115         String versUrlEntry = urlFile + "META-INF/versions/" + Runtime.version().major()
 116                 + "/version/Version.java";
 117         Assert.assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn));
 118         // adding any fragment does not change things
 119         Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn));
 120         Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn));
 121 
 122         // it really doesn't change things
 123         versUrlEntry = urlFile + "META-INF/versions/10/version/Version.java";
 124         Assert.assertTrue(readAndCompare(new URL(versUrlEntry), "return 10"));
 125         Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), "return 10"));
 126         Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), "return 10"));
 127     }
 128 
 129     @Test(dataProvider = "data")
 130     public void testCachedJars(String style, String file) throws Exception {
 131         String urlFile = "jar:file:" + file + "!/";
 132 
 133         URL rootUrl = new URL(urlFile);
 134         JarURLConnection juc = (JarURLConnection)rootUrl.openConnection();
 135         JarFile rootJar = juc.getJarFile();
 136         Runtime.Version root = rootJar.getVersion();
 137 
 138         URL runtimeUrl = new URL(urlFile + "#runtime");
 139         juc = (JarURLConnection)runtimeUrl.openConnection();
 140         JarFile runtimeJar = juc.getJarFile();
 141         Runtime.Version runtime = runtimeJar.getVersion();
 142         if (style.equals("unversioned")) {
 143             Assert.assertEquals(root, runtime);
 144         } else {
 145             Assert.assertNotEquals(root, runtime);
 146         }
 147 
 148         juc = (JarURLConnection)rootUrl.openConnection();
 149         JarFile jar = juc.getJarFile();
 150         Assert.assertEquals(jar.getVersion(), root);
 151         Assert.assertEquals(jar, rootJar);
 152 
 153         juc = (JarURLConnection)runtimeUrl.openConnection();
 154         jar = juc.getJarFile();
 155         Assert.assertEquals(jar.getVersion(), runtime);
 156         Assert.assertEquals(jar, runtimeJar);
 157 
 158         rootJar.close();
 159         runtimeJar.close();
 160         jar.close(); // probably not needed
 161     }
 162 
 163     @DataProvider(name = "resourcedata")
 164     public Object[][] createResourceData() throws Exception {
 165         return new Object[][]{
 166                 {"unversioned", Paths.get(unversioned).toUri().toURL()},
 167                 {"unsigned", Paths.get(unsigned).toUri().toURL()},
 168                 {"signed", Paths.get(signed).toUri().toURL()},
 169                 {"unversioned", new URL("file:" + unversioned)},
 170                 {"unsigned", new URL("file:" + unsigned)},
 171                 {"signed", new URL("file:" + signed)},
 172                 {"unversioned", new URL("jar:file:" + unversioned + "!/")},
 173                 {"unsigned", new URL("jar:file:" + unsigned + "!/")},
 174                 {"signed", new URL("jar:file:" + signed + "!/")},
 175                 // external jar received via http protocol
 176                 {"http", new URL("jar:http://localhost:" + server.getPort() + "/multi-release.jar!/")},
 177                 {"http", new URL("http://localhost:" + server.getPort() + "/multi-release.jar")},
 178 
 179         };
 180     }
 181 
 182     @Test(dataProvider = "resourcedata")
 183     public void testResources(String style, URL url) throws Throwable {
 184         //System.out.println("  testing " + style + " url: " + url);
 185         URL[] urls = {url};
 186         URLClassLoader cldr = new URLClassLoader(urls);
 187         Class<?> vcls = cldr.loadClass("version.Version");
 188 
 189         // verify we are loading a runtime versioned class
 190         MethodType mt = MethodType.methodType(int.class);
 191         MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt);
 192         Assert.assertEquals((int)mh.invoke(vcls.newInstance()),
 193                 style.equals("unversioned") ? 8 : Runtime.version().major());
 194 
 195         // now get a resource and verify that we don't have a fragment attached
 196         Enumeration<URL> vclsUrlEnum = cldr.getResources("version/Version.class");
 197         Assert.assertTrue(vclsUrlEnum.hasMoreElements());
 198         URL vclsUrls[] = new URL[] {
 199             vcls.getResource("/version/Version.class"),
 200             vcls.getResource("Version.class"),
 201             cldr.getResource("version/Version.class"),
 202             vclsUrlEnum.nextElement()
 203         };
 204         Assert.assertFalse(vclsUrlEnum.hasMoreElements());
 205         for (URL vclsUrl : vclsUrls) {
 206             String fragment = vclsUrl.getRef();
 207             Assert.assertNull(fragment);
 208 
 209             // and verify that the the url is a reified pointer to the runtime entry
 210             String rep = vclsUrl.toString();
 211             //System.out.println("    getResource(\"/version/Version.class\") returned: " + rep);
 212             if (style.equals("http")) {
 213                 Assert.assertTrue(rep.startsWith("jar:http:"));
 214             } else {
 215                 Assert.assertTrue(rep.startsWith("jar:file:"));
 216             }
 217             String suffix;
 218             if (style.equals("unversioned")) {
 219                 suffix = ".jar!/version/Version.class";
 220             } else {
 221                 suffix = ".jar!/META-INF/versions/" + Runtime.version().major()
 222                         + "/version/Version.class";
 223             }
 224             Assert.assertTrue(rep.endsWith(suffix));
 225         }
 226         cldr.close();
 227     }
 228 
 229 
 230     private boolean readAndCompare(URL url, String match) throws Exception {
 231         boolean result;
 232         // necessary to do it this way, instead of openStream(), so we can
 233         // close underlying JarFile, otherwise windows can't delete the file
 234         URLConnection conn = url.openConnection();
 235         try (InputStream is = conn.getInputStream()) {
 236             byte[] bytes = is.readAllBytes();
 237             result = (new String(bytes)).contains(match);
 238         }
 239         if (conn instanceof JarURLConnection) {
 240             ((JarURLConnection)conn).getJarFile().close();
 241         }
 242         return result;
 243     }
 244 }