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