/* * Copyright (c) 2011, 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. */ /* * @test * @bug 6887710 * @summary Verify the impact of sun.misc.JarIndex.metaInfFilenames on ServiceLoader * @modules jdk.jartool/sun.tools.jar * jdk.httpserver * jdk.compiler * @run main/othervm Basic */ import java.io.IOException; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; import java.util.Iterator; import java.util.ServiceLoader; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; /** * Verifies the impact of sun.misc.JarIndex.metaInfFilenames on ServiceLoader * and on finding resources via Class.getResource. * * 1) Compile the test sources: * jarA: * META-INF/services/my.happy.land * com/message/spi/MessageService.java * a/A.java * jarB: * META-INF/JAVA2.DS * META-INF/services/no.name.service * b/B.java * jarC: * META-INF/fonts.mf * META-INF/fonts/Company-corporate.ttf * META-INF/fonts/kidpr.ttf * META-INF/services/com.message.spi.MessageService * my/impl/StandardMessageService.java * * 2) Build three jar files a.jar, b.jar, c.jar * * 3) Create an index in a.jar (jar -i a.jar b.jar c.jar) * with sun.misc.JarIndex.metaInfFilenames=true * * 4) Start a HTTP server serving out the three jars. * * The test then tries to locate services/resources within the jars using * URLClassLoader. Each request to the HTTP server is recorded to ensure * only the correct amount of requests are being made. * */ public class Basic { static final String slash = File.separator; static final String[] testSources = { "jarA" + slash + "a" + slash + "A.java", "jarA" + slash + "com" + slash + "message" + slash + "spi" + slash + "MessageService.java", "jarB" + slash + "b" + slash + "B.java", "jarC" + slash + "my" + slash + "impl" + slash + "StandardMessageService.java"}; static final String testSrc = System.getProperty("test.src"); static final String testSrcDir = testSrc != null ? testSrc : "."; static final String testClasses = System.getProperty("test.classes"); static final String testClassesDir = testClasses != null ? testClasses : "."; static JarHttpServer httpServer; public static void main(String[] args) throws Exception { // Set global url cache to false so that we can track every jar request. (new URL("http://localhost/")).openConnection().setDefaultUseCaches(false); buildTest(); try { httpServer = new JarHttpServer(testClassesDir); httpServer.start(); doTest(httpServer.getAddress()); } catch (IOException ioe) { ioe.printStackTrace(); } finally { if (httpServer != null) { httpServer.stop(2); } } } static void buildTest() { /* compile the source that will be used to generate the jars */ for (int i=0; i<testSources.length; i++) testSources[i] = testSrcDir + slash + testSources[i]; compile("-d" , testClassesDir, "-sourcepath", testSrcDir, testSources[0], testSources[1], testSources[2], testSources[3]); /* build the 3 jar files */ jar("-cf", testClassesDir + slash + "a.jar", "-C", testClassesDir, "a", "-C", testClassesDir, "com", "-C", testSrcDir + slash + "jarA", "META-INF"); jar("-cf", testClassesDir + slash + "b.jar", "-C", testClassesDir, "b", "-C", testSrcDir + slash + "jarB", "META-INF"); jar("-cf", testClassesDir + slash + "c.jar", "-C", testClassesDir, "my", "-C", testSrcDir + slash + "jarC", "META-INF"); /* Create an index in a.jar for b.jar and c.jar */ createIndex(testClassesDir); } /* run jar <args> */ static void jar(String... args) { debug("Running: jar " + Arrays.toString(args)); sun.tools.jar.Main jar = new sun.tools.jar.Main(System.out, System.err, "jar"); if (!jar.run(args)) { throw new RuntimeException("jar failed: args=" + Arrays.toString(args)); } } /* run javac <args> */ static void compile(String... args) { debug("Running: javac " + Arrays.toString(args)); if (com.sun.tools.javac.Main.compile(args) != 0) { throw new RuntimeException("javac failed: args=" + Arrays.toString(args)); } } static String jar; static { jar = System.getProperty("java.home") + slash+ "bin" + slash + "jar"; } /* create the index */ static void createIndex(String workingDir) { // ProcessBuilder is used so that the current directory can be set // to the directory that directly contains the jars. debug("Running jar to create the index"); ProcessBuilder pb = new ProcessBuilder( jar, "-J-Dsun.misc.JarIndex.metaInfFilenames=true", "-i", "a.jar", "b.jar", "c.jar"); pb.directory(new File(workingDir)); //pd.inheritIO(); try { Process p = pb.start(); if(p.waitFor() != 0) throw new RuntimeException("jar indexing failed"); if(debug && p != null) { String line = null; BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); while((line = reader.readLine()) != null) debug(line); reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); while((line = reader.readLine()) != null) debug(line); } } catch(InterruptedException ie) { throw new RuntimeException(ie); } catch(IOException e) { throw new RuntimeException(e); } } static final boolean debug = true; static void debug(Object message) { if (debug) System.out.println(message); } /* service define in c.jar */ static final String messageService = "com.message.spi.MessageService"; /* a service that is not defined in any of the jars */ static final String unknownService = "java.lang.Object"; static void doTest(InetSocketAddress serverAddress) throws IOException { URL baseURL = new URL("http://localhost:" + serverAddress.getPort() + "/"); int failed = 0; // Tests using java.util.SerivceLoader if (!javaUtilServiceLoaderTest(baseURL, messageService, true, false, true)) { System.out.println("Test: ServiceLoader looking for " + messageService + ", failed"); failed++; } if (!javaUtilServiceLoaderTest(baseURL, unknownService, false, false, false)) { System.out.println("Test: ServiceLoader looking for " + unknownService + " failed"); failed++; } // Tests using java.lang.Class (similar to the FontManager in javafx) if (!klassLoader(baseURL, "/META-INF/fonts.mf", true, false, true)) { System.out.println("Test: klassLoader looking for /META-INF/fonts.mf failed"); failed++; } if (!klassLoader(baseURL, "/META-INF/unknown.mf", false, false, false)) { System.out.println("Test: klassLoader looking for /META-INF/unknown.mf failed"); failed++; } if (failed > 0) throw new RuntimeException("Failed: " + failed + " tests"); } static boolean javaUtilServiceLoaderTest(URL baseURL, String serviceClass, boolean expectToFind, boolean expectbDotJar, boolean expectcDotJar) throws IOException { debug("----------------------------------"); debug("Running test with java.util.ServiceLoader looking for " + serviceClass); URLClassLoader loader = getLoader(baseURL); httpServer.reset(); Class<?> messageServiceClass = null; try { messageServiceClass = loader.loadClass(serviceClass); } catch (ClassNotFoundException cnfe) { System.err.println(cnfe); throw new RuntimeException("Error in test: " + cnfe); } Iterator<?> iterator = (ServiceLoader.load(messageServiceClass, loader)).iterator(); if (expectToFind && !iterator.hasNext()) { debug(messageServiceClass + " NOT found."); return false; } while (iterator.hasNext()) { debug("found " + iterator.next() + " " + messageService); } debug("HttpServer: " + httpServer); if (!expectbDotJar && httpServer.bDotJar > 0) { debug("Unexpeced request sent to the httpserver for b.jar"); return false; } if (!expectcDotJar && httpServer.cDotJar > 0) { debug("Unexpeced request sent to the httpserver for c.jar"); return false; } return true; } /* Tries to find a resource in a similar way to the font manager in javafx * com.sun.javafx.scene.text.FontManager */ static boolean klassLoader(URL baseURL, String resource, boolean expectToFind, boolean expectbDotJar, boolean expectcDotJar) throws IOException { debug("----------------------------------"); debug("Running test looking for " + resource); URLClassLoader loader = getLoader(baseURL); httpServer.reset(); Class<?> ADotAKlass = null; try { ADotAKlass = loader.loadClass("a.A"); } catch (ClassNotFoundException cnfe) { System.err.println(cnfe); throw new RuntimeException("Error in test: " + cnfe); } URL u = ADotAKlass.getResource(resource); if (expectToFind && u == null) { System.out.println("Expected to find " + resource + " but didn't"); return false; } debug("HttpServer: " + httpServer); if (!expectbDotJar && httpServer.bDotJar > 0) { debug("Unexpeced request sent to the httpserver for b.jar"); return false; } if (!expectcDotJar && httpServer.cDotJar > 0) { debug("Unexpeced request sent to the httpserver for c.jar"); return false; } return true; } static URLClassLoader getLoader(URL baseURL) throws IOException { ClassLoader loader = Basic.class.getClassLoader(); while (loader.getParent() != null) loader = loader.getParent(); return new URLClassLoader( new URL[]{ new URL(baseURL, "a.jar"), new URL(baseURL, "b.jar"), new URL(baseURL, "c.jar")}, loader ); } /** * HTTP Server to server the jar files. */ static class JarHttpServer implements HttpHandler { final String docsDir; final HttpServer httpServer; int aDotJar, bDotJar, cDotJar; JarHttpServer(String docsDir) throws IOException { this.docsDir = docsDir; httpServer = HttpServer.create(new InetSocketAddress(0), 0); httpServer.createContext("/", this); } void start() throws IOException { httpServer.start(); } void stop(int delay) { httpServer.stop(delay); } InetSocketAddress getAddress() { return httpServer.getAddress(); } void reset() { aDotJar = bDotJar = cDotJar = 0; } @Override public String toString() { return "aDotJar=" + aDotJar + ", bDotJar=" + bDotJar + ", cDotJar=" + cDotJar; } public void handle(HttpExchange t) throws IOException { InputStream is = t.getRequestBody(); Headers map = t.getRequestHeaders(); Headers rmap = t.getResponseHeaders(); URI uri = t.getRequestURI(); debug("Server: received request for " + uri); String path = uri.getPath(); if (path.endsWith("a.jar")) aDotJar++; else if (path.endsWith("b.jar")) bDotJar++; else if (path.endsWith("c.jar")) cDotJar++; else System.out.println("Unexpected resource request" + path); while (is.read() != -1); is.close(); File file = new File(docsDir, path); if (!file.exists()) throw new RuntimeException("Error: request for " + file); long clen = file.length(); t.sendResponseHeaders (200, clen); OutputStream os = t.getResponseBody(); FileInputStream fis = new FileInputStream(file); try { byte[] buf = new byte [16 * 1024]; int len; while ((len=fis.read(buf)) != -1) { os.write (buf, 0, len); } } catch (IOException e) { e.printStackTrace(); } fis.close(); os.close(); } } }