1 /*
   2  * Copyright (c) 2011, 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 6887710
  27  * @summary Verify the impact of sun.misc.JarIndex.metaInfFilenames on ServiceLoader
  28  * @run main/othervm Basic
  29  */
  30 
  31 import java.io.IOException;
  32 import java.io.BufferedReader;
  33 import java.io.File;
  34 import java.io.FileInputStream;
  35 import java.io.InputStream;
  36 import java.io.InputStreamReader;
  37 import java.io.OutputStream;
  38 import java.net.InetSocketAddress;
  39 import java.net.URI;
  40 import java.net.URL;
  41 import java.net.URLClassLoader;
  42 import java.util.Arrays;
  43 import java.util.Iterator;
  44 import java.util.ServiceLoader;
  45 import com.sun.net.httpserver.Headers;
  46 import com.sun.net.httpserver.HttpExchange;
  47 import com.sun.net.httpserver.HttpHandler;
  48 import com.sun.net.httpserver.HttpServer;
  49 
  50 /**
  51  * Verifies the impact of sun.misc.JarIndex.metaInfFilenames on ServiceLoader
  52  * and on finding resources via Class.getResource.
  53  *
  54  * 1) Compile the test sources:
  55  *   jarA:
  56  *     META-INF/services/my.happy.land
  57  *     com/message/spi/MessageService.java
  58  *     a/A.java
  59  *   jarB:
  60  *     META-INF/JAVA2.DS
  61  *     META-INF/services/no.name.service
  62  *     b/B.java
  63  *   jarC:
  64  *     META-INF/fonts.mf
  65  *     META-INF/fonts/Company-corporate.ttf
  66  *     META-INF/fonts/kidpr.ttf
  67  *     META-INF/services/com.message.spi.MessageService
  68  *     my/impl/StandardMessageService.java
  69  *
  70  * 2) Build three jar files a.jar, b.jar, c.jar
  71  *
  72  * 3) Create an index in a.jar (jar -i a.jar b.jar c.jar)
  73  *      with sun.misc.JarIndex.metaInfFilenames=true
  74  *
  75  * 4) Start a HTTP server serving out the three jars.
  76  *
  77  * The test then tries to locate services/resources within the jars using
  78  * URLClassLoader. Each request to the HTTP server is recorded to ensure
  79  * only the correct amount of requests are being made.
  80  *
  81  * Note: Needs jdk/lib/tools.jar in the classpath to compile and run.
  82  */
  83 
  84 public class Basic {
  85     static final String slash = File.separator;
  86     static final String[] testSources =  {
  87          "jarA" + slash + "a" + slash + "A.java",
  88          "jarA" + slash + "com" + slash + "message" + slash + "spi" + slash + "MessageService.java",
  89          "jarB" + slash + "b" + slash + "B.java",
  90          "jarC" + slash + "my" + slash + "impl" + slash + "StandardMessageService.java"};
  91 
  92     static final String testSrc = System.getProperty("test.src");
  93     static final String testSrcDir = testSrc != null ? testSrc : ".";
  94     static final String testClasses = System.getProperty("test.classes");
  95     static final String testClassesDir = testClasses != null ? testClasses : ".";
  96 
  97     static JarHttpServer httpServer;
  98 
  99     public static void main(String[] args) throws Exception {
 100 
 101         // Set global url cache to false so that we can track every jar request.
 102         (new URL("http://localhost/")).openConnection().setDefaultUseCaches(false);
 103 
 104         buildTest();
 105 
 106         try {
 107             httpServer = new JarHttpServer(testClassesDir);
 108             httpServer.start();
 109 
 110             doTest(httpServer.getAddress());
 111 
 112         } catch (IOException ioe) {
 113             ioe.printStackTrace();
 114         } finally {
 115             if (httpServer != null) { httpServer.stop(2); }
 116         }
 117     }
 118 
 119     static void buildTest() {
 120         /* compile the source that will be used to generate the jars */
 121         for (int i=0; i<testSources.length; i++)
 122             testSources[i] = testSrcDir + slash + testSources[i];
 123 
 124         compile("-d" , testClassesDir,
 125                 "-sourcepath", testSrcDir,
 126                 testSources[0], testSources[1], testSources[2], testSources[3]);
 127 
 128         /* build the 3 jar files */
 129         jar("-cf", testClassesDir + slash + "a.jar",
 130             "-C", testClassesDir, "a",
 131             "-C", testClassesDir, "com",
 132             "-C", testSrcDir + slash + "jarA", "META-INF");
 133         jar("-cf", testClassesDir + slash + "b.jar",
 134             "-C", testClassesDir, "b",
 135             "-C", testSrcDir + slash + "jarB", "META-INF");
 136         jar("-cf", testClassesDir + slash + "c.jar",
 137             "-C", testClassesDir, "my",
 138             "-C", testSrcDir + slash + "jarC", "META-INF");
 139 
 140         /* Create an index in a.jar for b.jar and c.jar */
 141         createIndex(testClassesDir);
 142     }
 143 
 144     /* run jar <args> */
 145     static void jar(String... args) {
 146         debug("Running: jar " + Arrays.toString(args));
 147         sun.tools.jar.Main jar = new sun.tools.jar.Main(System.out, System.err, "jar");
 148         if (!jar.run(args)) {
 149             throw new RuntimeException("jar failed: args=" + Arrays.toString(args));
 150         }
 151     }
 152 
 153     /* run javac <args> */
 154     static void compile(String... args) {
 155         debug("Running: javac " + Arrays.toString(args));
 156         if (com.sun.tools.javac.Main.compile(args) != 0) {
 157              throw new RuntimeException("javac failed: args=" + Arrays.toString(args));
 158         }
 159     }
 160 
 161     static String jar;
 162     static {
 163         String javaHome = System.getProperty("java.home");
 164         if (javaHome.endsWith("jre")) {
 165             int index = javaHome.lastIndexOf(slash);
 166             if (index != -1)
 167                 javaHome = javaHome.substring(0, index);
 168         }
 169 
 170         jar = javaHome + slash+  "bin" + slash + "jar";
 171     }
 172 
 173     /* create the index */
 174     static void createIndex(String workingDir) {
 175         // ProcessBuilder is used so that the current directory can be set
 176         // to the directory that directly contains the jars.
 177         debug("Running jar to create the index");
 178         ProcessBuilder pb = new ProcessBuilder(
 179            jar, "-J-Dsun.misc.JarIndex.metaInfFilenames=true", "-i", "a.jar", "b.jar", "c.jar");
 180         pb.directory(new File(workingDir));
 181         //pd.inheritIO();
 182         try {
 183             Process p = pb.start();
 184             if(p.waitFor() != 0)
 185                 throw new RuntimeException("jar indexing failed");
 186 
 187             if(debug && p != null) {
 188                 String line = null;
 189                 BufferedReader reader =
 190                          new BufferedReader(new InputStreamReader(p.getInputStream()));
 191                 while((line = reader.readLine()) != null)
 192                     debug(line);
 193                 reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
 194                 while((line = reader.readLine()) != null)
 195                     debug(line);
 196             }
 197         } catch(InterruptedException ie) { throw new RuntimeException(ie);
 198         } catch(IOException e) { throw new RuntimeException(e); }
 199     }
 200 
 201     static final boolean debug = true;
 202 
 203     static void debug(Object message) { if (debug) System.out.println(message); }
 204 
 205     /* service define in c.jar */
 206     static final String messageService = "com.message.spi.MessageService";
 207 
 208     /* a service that is not defined in any of the jars */
 209     static final String unknownService = "java.lang.Object";
 210 
 211     static void doTest(InetSocketAddress serverAddress) throws IOException {
 212         URL baseURL = new URL("http://localhost:" + serverAddress.getPort() + "/");
 213 
 214         int failed = 0;
 215 
 216         // Tests using java.util.SerivceLoader
 217         if (!javaUtilServiceLoaderTest(baseURL, messageService, true, false, true)) {
 218             System.out.println("Test: ServiceLoader looking for " + messageService + ", failed");
 219             failed++;
 220         }
 221         if (!javaUtilServiceLoaderTest(baseURL, unknownService, false, false, false)) {
 222             System.out.println("Test: ServiceLoader looking for " + unknownService + " failed");
 223             failed++;
 224         }
 225 
 226         // Tests using java.lang.Class (similar to the FontManager in javafx)
 227         if (!klassLoader(baseURL, "/META-INF/fonts.mf", true, false, true)) {
 228             System.out.println("Test: klassLoader looking for /META-INF/fonts.mf failed");
 229             failed++;
 230         }
 231         if (!klassLoader(baseURL, "/META-INF/unknown.mf", false, false, false)) {
 232             System.out.println("Test: klassLoader looking for /META-INF/unknown.mf failed");
 233             failed++;
 234         }
 235 
 236         if (failed > 0)
 237             throw new RuntimeException("Failed: " + failed + " tests");
 238     }
 239 
 240     static boolean javaUtilServiceLoaderTest(URL baseURL,
 241                                              String serviceClass,
 242                                              boolean expectToFind,
 243                                              boolean expectbDotJar,
 244                                              boolean expectcDotJar) throws IOException {
 245         debug("----------------------------------");
 246         debug("Running test with java.util.ServiceLoader looking for " + serviceClass);
 247         URLClassLoader loader = getLoader(baseURL);
 248         httpServer.reset();
 249 
 250         Class<?> messageServiceClass = null;
 251         try {
 252             messageServiceClass = loader.loadClass(serviceClass);
 253         } catch (ClassNotFoundException cnfe) {
 254             System.err.println(cnfe);
 255             throw new RuntimeException("Error in test: " + cnfe);
 256         }
 257 
 258         Iterator<?> iterator = (ServiceLoader.load(messageServiceClass, loader)).iterator();
 259         if (expectToFind && !iterator.hasNext()) {
 260             debug(messageServiceClass + " NOT found.");
 261             return false;
 262         }
 263 
 264         while (iterator.hasNext()) {
 265             debug("found " + iterator.next() + " " + messageService);
 266         }
 267 
 268         debug("HttpServer: " + httpServer);
 269 
 270         if (!expectbDotJar && httpServer.bDotJar > 0) {
 271             debug("Unexpeced request sent to the httpserver for b.jar");
 272             return false;
 273         }
 274         if (!expectcDotJar && httpServer.cDotJar > 0) {
 275             debug("Unexpeced request sent to the httpserver for c.jar");
 276             return false;
 277         }
 278 
 279         return true;
 280     }
 281 
 282     /* Tries to find a resource in a similar way to the font manager in javafx
 283      * com.sun.javafx.scene.text.FontManager */
 284     static boolean klassLoader(URL baseURL,
 285                                String resource,
 286                                boolean expectToFind,
 287                                boolean expectbDotJar,
 288                                boolean expectcDotJar) throws IOException {
 289         debug("----------------------------------");
 290         debug("Running test looking for " + resource);
 291         URLClassLoader loader = getLoader(baseURL);
 292         httpServer.reset();
 293 
 294         Class<?> ADotAKlass = null;
 295         try {
 296             ADotAKlass = loader.loadClass("a.A");
 297         } catch (ClassNotFoundException cnfe) {
 298             System.err.println(cnfe);
 299             throw new RuntimeException("Error in test: " + cnfe);
 300         }
 301 
 302         URL u = ADotAKlass.getResource(resource);
 303         if (expectToFind && u == null) {
 304             System.out.println("Expected to find " + resource + " but didn't");
 305             return false;
 306         }
 307 
 308         debug("HttpServer: " + httpServer);
 309 
 310         if (!expectbDotJar && httpServer.bDotJar > 0) {
 311             debug("Unexpeced request sent to the httpserver for b.jar");
 312             return false;
 313         }
 314         if (!expectcDotJar && httpServer.cDotJar > 0) {
 315             debug("Unexpeced request sent to the httpserver for c.jar");
 316             return false;
 317         }
 318 
 319         return true;
 320     }
 321 
 322     static URLClassLoader getLoader(URL baseURL) throws IOException {
 323         ClassLoader loader = Basic.class.getClassLoader();
 324 
 325         while (loader.getParent() != null)
 326             loader = loader.getParent();
 327 
 328         return new URLClassLoader( new URL[]{
 329             new URL(baseURL, "a.jar"),
 330             new URL(baseURL, "b.jar"),
 331             new URL(baseURL, "c.jar")}, loader );
 332     }
 333 
 334     /**
 335      * HTTP Server to server the jar files.
 336      */
 337     static class JarHttpServer implements HttpHandler {
 338         final String docsDir;
 339         final HttpServer httpServer;
 340         int aDotJar, bDotJar, cDotJar;
 341 
 342         JarHttpServer(String docsDir) throws IOException {
 343             this.docsDir = docsDir;
 344 
 345             httpServer = HttpServer.create(new InetSocketAddress(0), 0);
 346             httpServer.createContext("/", this);
 347         }
 348 
 349         void start() throws IOException {
 350             httpServer.start();
 351         }
 352 
 353         void stop(int delay) {
 354             httpServer.stop(delay);
 355         }
 356 
 357         InetSocketAddress getAddress() {
 358             return httpServer.getAddress();
 359         }
 360 
 361         void reset() {
 362             aDotJar = bDotJar = cDotJar = 0;
 363         }
 364 
 365         @Override
 366         public String toString() {
 367             return "aDotJar=" + aDotJar + ", bDotJar=" + bDotJar + ", cDotJar=" + cDotJar;
 368         }
 369 
 370         public void handle(HttpExchange t) throws IOException {
 371             InputStream is = t.getRequestBody();
 372             Headers map = t.getRequestHeaders();
 373             Headers rmap = t.getResponseHeaders();
 374             URI uri = t.getRequestURI();
 375 
 376             debug("Server: received request for " + uri);
 377             String path = uri.getPath();
 378             if (path.endsWith("a.jar"))
 379                 aDotJar++;
 380             else if (path.endsWith("b.jar"))
 381                 bDotJar++;
 382             else if (path.endsWith("c.jar"))
 383                 cDotJar++;
 384             else
 385                 System.out.println("Unexpected resource request" + path);
 386 
 387             while (is.read() != -1);
 388             is.close();
 389 
 390             File file = new File(docsDir, path);
 391             if (!file.exists())
 392                 throw new RuntimeException("Error: request for " + file);
 393             long clen = file.length();
 394             t.sendResponseHeaders (200, clen);
 395             OutputStream os = t.getResponseBody();
 396             FileInputStream fis = new FileInputStream(file);
 397             try {
 398                 byte[] buf = new byte [16 * 1024];
 399                 int len;
 400                 while ((len=fis.read(buf)) != -1) {
 401                     os.write (buf, 0, len);
 402                 }
 403             } catch (IOException e) {
 404                 e.printStackTrace();
 405             }
 406             fis.close();
 407             os.close();
 408         }
 409     }
 410 }