MultiReleaseJarURLConnection.java revision 14606:bc3775e25b52
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
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.httpserver
31 *          jdk.jartool
32 * @build Compiler JarBuilder CreateMultiReleaseTestJars SimpleHttpServer
33 * @run testng MultiReleaseJarURLConnection
34 */
35
36import java.io.IOException;
37import java.io.InputStream;
38import java.lang.invoke.MethodHandle;
39import java.lang.invoke.MethodHandles;
40import java.lang.invoke.MethodType;
41import java.net.JarURLConnection;
42import java.net.URL;
43import java.net.URLClassLoader;
44import java.net.URLConnection;
45import java.nio.file.Files;
46import java.nio.file.Paths;
47import java.util.jar.JarFile;
48
49import org.testng.Assert;
50import org.testng.annotations.AfterClass;
51import org.testng.annotations.BeforeClass;
52import org.testng.annotations.DataProvider;
53import org.testng.annotations.Test;
54
55public class MultiReleaseJarURLConnection {
56    String userdir = System.getProperty("user.dir",".");
57    String unversioned = userdir + "/unversioned.jar";
58    String unsigned = userdir + "/multi-release.jar";
59    String signed = userdir + "/signed-multi-release.jar";
60    SimpleHttpServer server;
61
62    @BeforeClass
63    public void initialize() throws Exception {
64        CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars();
65        creator.compileEntries();
66        creator.buildUnversionedJar();
67        creator.buildMultiReleaseJar();
68        creator.buildSignedMultiReleaseJar();
69
70        server = new SimpleHttpServer();
71        server.start();
72
73    }
74
75    @AfterClass
76    public void close() throws IOException {
77        // Windows requires server to stop before file is deleted
78        if (server != null)
79            server.stop();
80        Files.delete(Paths.get(unversioned));
81        Files.delete(Paths.get(unsigned));
82        Files.delete(Paths.get(signed));
83    }
84
85    @DataProvider(name = "data")
86    public Object[][] createData() {
87        return new Object[][]{
88                {"unversioned", unversioned},
89                {"unsigned", unsigned},
90                {"signed", signed}
91        };
92    }
93
94    @Test(dataProvider = "data")
95    public void testRuntimeVersioning(String style, String file) throws Exception {
96        String urlFile = "jar:file:" + file + "!/";
97        String baseUrlEntry = urlFile + "version/Version.java";
98        String rtreturn = "return " + Runtime.version().major();
99
100        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
101        // #runtime is "magic" for a multi-release jar, but not for unversioned jar
102        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"),
103                style.equals("unversioned") ? "return 8" : rtreturn));
104        // #fragment or any other fragment is not magic
105        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8"));
106        // cached entities not affected
107        Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8"));
108
109        // the following tests will not work with unversioned jars
110        if (style.equals("unversioned")) return;
111
112        // direct access to versioned entry
113        String versUrlEntry = urlFile + "META-INF/versions/" + Runtime.version().major()
114                + "/version/Version.java";
115        Assert.assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn));
116        // adding any fragment does not change things
117        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn));
118        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn));
119
120        // it really doesn't change things
121        versUrlEntry = urlFile + "META-INF/versions/10/version/Version.java";
122        Assert.assertTrue(readAndCompare(new URL(versUrlEntry), "return 10"));
123        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), "return 10"));
124        Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), "return 10"));
125    }
126
127    @Test(dataProvider = "data")
128    public void testCachedJars(String style, String file) throws Exception {
129        String urlFile = "jar:file:" + file + "!/";
130
131        URL rootUrl = new URL(urlFile);
132        JarURLConnection juc = (JarURLConnection)rootUrl.openConnection();
133        JarFile rootJar = juc.getJarFile();
134        JarFile.Release root = rootJar.getVersion();
135
136        URL runtimeUrl = new URL(urlFile + "#runtime");
137        juc = (JarURLConnection)runtimeUrl.openConnection();
138        JarFile runtimeJar = juc.getJarFile();
139        JarFile.Release runtime = runtimeJar.getVersion();
140        if (style.equals("unversioned")) {
141            Assert.assertEquals(root, runtime);
142        } else {
143            Assert.assertNotEquals(root, runtime);
144        }
145
146        juc = (JarURLConnection)rootUrl.openConnection();
147        JarFile jar = juc.getJarFile();
148        Assert.assertEquals(jar.getVersion(), root);
149        Assert.assertEquals(jar, rootJar);
150
151        juc = (JarURLConnection)runtimeUrl.openConnection();
152        jar = juc.getJarFile();
153        Assert.assertEquals(jar.getVersion(), runtime);
154        Assert.assertEquals(jar, runtimeJar);
155
156        rootJar.close();
157        runtimeJar.close();
158        jar.close(); // probably not needed
159    }
160
161    @DataProvider(name = "resourcedata")
162    public Object[][] createResourceData() throws Exception {
163        return new Object[][]{
164                {"unversioned", Paths.get(unversioned).toUri().toURL()},
165                {"unsigned", Paths.get(unsigned).toUri().toURL()},
166                {"signed", Paths.get(signed).toUri().toURL()},
167                {"unversioned", new URL("file:" + unversioned)},
168                {"unsigned", new URL("file:" + unsigned)},
169                {"signed", new URL("file:" + signed)},
170                {"unversioned", new URL("jar:file:" + unversioned + "!/")},
171                {"unsigned", new URL("jar:file:" + unsigned + "!/")},
172                {"signed", new URL("jar:file:" + signed + "!/")},
173                // external jar received via http protocol
174                {"http", new URL("jar:http://localhost:" + server.getPort() + "/multi-release.jar!/")},
175                {"http", new URL("http://localhost:" + server.getPort() + "/multi-release.jar")},
176
177        };
178    }
179
180    @Test(dataProvider = "resourcedata")
181    public void testResources(String style, URL url) throws Throwable {
182        //System.out.println("  testing " + style + " url: " + url);
183        URL[] urls = {url};
184        URLClassLoader cldr = new URLClassLoader(urls);
185        Class<?> vcls = cldr.loadClass("version.Version");
186
187        // verify we are loading a runtime versioned class
188        MethodType mt = MethodType.methodType(int.class);
189        MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt);
190        Assert.assertEquals((int)mh.invoke(vcls.newInstance()),
191                style.equals("unversioned") ? 8 : Runtime.version().major());
192
193        // now get a resource and verify that we don't have a fragment attached
194        URL vclsUrl = vcls.getResource("/version/Version.class");
195        String fragment = vclsUrl.getRef();
196        Assert.assertNull(fragment);
197
198        // and verify that the the url is a reified pointer to the runtime entry
199        String rep = vclsUrl.toString();
200        //System.out.println("    getResource(\"/version/Version.class\") returned: " + rep);
201        if (style.equals("http")) {
202            Assert.assertTrue(rep.startsWith("jar:http:"));
203        } else {
204            Assert.assertTrue(rep.startsWith("jar:file:"));
205        }
206        String suffix;
207        if (style.equals("unversioned")) {
208            suffix = ".jar!/version/Version.class";
209        } else {
210            suffix = ".jar!/META-INF/versions/" + Runtime.version().major()
211                    + "/version/Version.class";
212        }
213        Assert.assertTrue(rep.endsWith(suffix));
214        cldr.close();
215    }
216
217
218    private boolean readAndCompare(URL url, String match) throws Exception {
219        boolean result;
220        // necessary to do it this way, instead of openStream(), so we can
221        // close underlying JarFile, otherwise windows can't delete the file
222        URLConnection conn = url.openConnection();
223        try (InputStream is = conn.getInputStream()) {
224            byte[] bytes = is.readAllBytes();
225            result = (new String(bytes)).contains(match);
226        }
227        if (conn instanceof JarURLConnection) {
228            ((JarURLConnection)conn).getJarFile().close();
229        }
230        return result;
231    }
232}
233