1 /*
   2  * Copyright (c) 2016, 2017, 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  * @modules java.scripting
  27  * @library modules /test/lib
  28  * @build bananascript/*
  29  * @build jdk.test.lib.util.JarUtils
  30  * @compile classpath/pearscript/org/pear/PearScriptEngineFactory.java
  31  *          classpath/pearscript/org/pear/PearScript.java
  32  * @run testng/othervm ModulesTest
  33  * @summary Basic test for ServiceLoader with a provider deployed as a module.
  34  */
  35 
  36 import java.io.File;
  37 import java.lang.module.Configuration;
  38 import java.lang.module.ModuleFinder;
  39 import java.nio.file.Files;
  40 import java.nio.file.Path;
  41 import java.nio.file.Paths;
  42 import java.nio.file.StandardCopyOption;
  43 import java.util.ArrayList;
  44 import java.util.Collections;
  45 import java.util.HashSet;
  46 import java.util.Iterator;
  47 import java.util.List;
  48 import java.util.Optional;
  49 import java.util.ServiceLoader;
  50 import java.util.ServiceLoader.Provider;
  51 import java.util.Set;
  52 import java.util.stream.Collectors;
  53 import java.util.stream.Stream;
  54 import javax.script.ScriptEngineFactory;
  55 
  56 import jdk.test.lib.util.JarUtils;
  57 
  58 import org.testng.annotations.Test;
  59 import org.testng.annotations.BeforeTest;
  60 import static org.testng.Assert.*;
  61 
  62 /**
  63  * Basic test for ServiceLoader. The test make use of two service providers:
  64  * 1. BananaScriptEngine - a ScriptEngineFactory deployed as a module on the
  65  *    module path. It implementations a singleton via the public static
  66  *    provider method.
  67  * 2. PearScriptEngine - a ScriptEngineFactory deployed on the class path
  68  *    with a service configuration file.
  69  */
  70 
  71 public class ModulesTest {
  72 
  73     // Copy the services configuration file for "pearscript" into place.
  74     @BeforeTest
  75     public void setup() throws Exception {
  76         Path src = Paths.get(System.getProperty("test.src"));
  77         Path classes = Paths.get(System.getProperty("test.classes"));
  78         String st = ScriptEngineFactory.class.getName();
  79         Path config = Paths.get("META-INF", "services", st);
  80         Path source = src.resolve("classpath").resolve("pearscript").resolve(config);
  81         Path target = classes.resolve(config);
  82         Files.createDirectories(target.getParent());
  83         Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
  84     }
  85 
  86     /**
  87      * Basic test of iterator() to ensure that providers located as modules
  88      * and on the class path are found.
  89      */
  90     @Test
  91     public void testIterator() {
  92         ServiceLoader<ScriptEngineFactory> loader
  93             = ServiceLoader.load(ScriptEngineFactory.class);
  94         Set<String> names = collectAll(loader)
  95                 .stream()
  96                 .map(ScriptEngineFactory::getEngineName)
  97                 .collect(Collectors.toSet());
  98         assertTrue(names.contains("BananaScriptEngine"));
  99         assertTrue(names.contains("PearScriptEngine"));
 100     }
 101 
 102     /**
 103      * Basic test of iterator() to test iteration order. Providers deployed
 104      * as named modules should be found before providers deployed on the class
 105      * path.
 106      */
 107     @Test
 108     public void testIteratorOrder() {
 109         ServiceLoader<ScriptEngineFactory> loader
 110             = ServiceLoader.load(ScriptEngineFactory.class);
 111         boolean foundUnnamed = false;
 112         for (ScriptEngineFactory factory : collectAll(loader)) {
 113             if (factory.getClass().getModule().isNamed()) {
 114                 if (foundUnnamed) {
 115                     assertTrue(false, "Named module element after unnamed");
 116                 }
 117             } else {
 118                 foundUnnamed = true;
 119             }
 120         }
 121     }
 122 
 123     /**
 124      * Basic test of Provider::type
 125      */
 126     @Test
 127     public void testProviderType() {
 128         Set<String> types = ServiceLoader.load(ScriptEngineFactory.class)
 129                 .stream()
 130                 .map(Provider::type)
 131                 .map(Class::getName)
 132                 .collect(Collectors.toSet());
 133         assertTrue(types.contains("org.banana.BananaScriptEngineFactory"));
 134         assertTrue(types.contains("org.pear.PearScriptEngineFactory"));
 135     }
 136 
 137     /**
 138      * Basic test of Provider::get
 139      */
 140     @Test
 141     public void testProviderGet() {
 142         Set<String> names = ServiceLoader.load(ScriptEngineFactory.class)
 143                 .stream()
 144                 .map(Provider::get)
 145                 .map(ScriptEngineFactory::getEngineName)
 146                 .collect(Collectors.toSet());
 147         assertTrue(names.contains("BananaScriptEngine"));
 148         assertTrue(names.contains("PearScriptEngine"));
 149     }
 150 
 151     /**
 152      * Basic test of the public static provider method. BananaScriptEngine
 153      * defines a provider method that returns the same instance.
 154      */
 155     @Test
 156     public void testSingleton() {
 157         Optional<Provider<ScriptEngineFactory>> oprovider
 158             = ServiceLoader.load(ScriptEngineFactory.class)
 159                 .stream()
 160                 .filter(p -> p.type().getName().equals("org.banana.BananaScriptEngineFactory"))
 161                 .findFirst();
 162         assertTrue(oprovider.isPresent());
 163         Provider<ScriptEngineFactory> provider = oprovider.get();
 164 
 165         // invoke Provider::get twice
 166         ScriptEngineFactory factory1 = provider.get();
 167         ScriptEngineFactory factory2 = provider.get();
 168         assertTrue(factory1 == factory2);
 169     }
 170 
 171     /**
 172      * Basic test of stream() to ensure that elements for providers in named
 173      * modules come before elements for providers in unnamed modules.
 174      */
 175     @Test
 176     public void testStreamOrder() {
 177         List<Class<?>> types = ServiceLoader.load(ScriptEngineFactory.class)
 178                 .stream()
 179                 .map(Provider::type)
 180                 .collect(Collectors.toList());
 181 
 182         boolean foundUnnamed = false;
 183         for (Class<?> factoryClass : types) {
 184             if (factoryClass.getModule().isNamed()) {
 185                 if (foundUnnamed) {
 186                     assertTrue(false, "Named module element after unnamed");
 187                 }
 188             } else {
 189                 foundUnnamed = true;
 190             }
 191         }
 192     }
 193 
 194     /**
 195      * Basic test of ServiceLoader.findFirst()
 196      */
 197     @Test
 198     public void testFindFirst() {
 199         Optional<ScriptEngineFactory> ofactory
 200             = ServiceLoader.load(ScriptEngineFactory.class).findFirst();
 201         assertTrue(ofactory.isPresent());
 202         ScriptEngineFactory factory = ofactory.get();
 203         assertTrue(factory.getClass().getModule().isNamed());
 204 
 205         class S { }
 206         assertFalse(ServiceLoader.load(S.class).findFirst().isPresent());
 207     }
 208 
 209     /**
 210      * Basic test ServiceLoader.load specifying the platform class loader.
 211      * The providers on the module path and class path should not be located.
 212      */
 213     @Test
 214     public void testWithPlatformClassLoader() {
 215         ClassLoader pcl = ClassLoader.getPlatformClassLoader();
 216 
 217         // iterator
 218         ServiceLoader<ScriptEngineFactory> loader
 219             = ServiceLoader.load(ScriptEngineFactory.class, pcl);
 220         Set<String> names = collectAll(loader)
 221                 .stream()
 222                 .map(ScriptEngineFactory::getEngineName)
 223                 .collect(Collectors.toSet());
 224         assertFalse(names.contains("BananaScriptEngine"));
 225         assertFalse(names.contains("PearScriptEngine"));
 226 
 227         // stream
 228         names = ServiceLoader.load(ScriptEngineFactory.class, pcl)
 229                 .stream()
 230                 .map(Provider::get)
 231                 .map(ScriptEngineFactory::getEngineName)
 232                 .collect(Collectors.toSet());
 233         assertFalse(names.contains("BananaScriptEngine"));
 234         assertFalse(names.contains("PearScriptEngine"));
 235     }
 236 
 237     /**
 238      * Basic test of ServiceLoader.load where the service provider module is an
 239      * automatic module.
 240      */
 241     @Test
 242     public void testWithAutomaticModule() throws Exception {
 243         Path here = Paths.get("");
 244         Path jar = Files.createTempDirectory(here, "lib").resolve("pearscript.jar");
 245         Path classes = Paths.get(System.getProperty("test.classes"));
 246 
 247         JarUtils.createJarFile(jar, classes, "META-INF", "org");
 248 
 249         ModuleFinder finder = ModuleFinder.of(jar);
 250         ModuleLayer bootLayer = ModuleLayer.boot();
 251         Configuration parent = bootLayer.configuration();
 252         Configuration cf = parent.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 253         assertTrue(cf.modules().size() == 1);
 254 
 255         ClassLoader scl = ClassLoader.getSystemClassLoader();
 256         ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 257         assertTrue(layer.modules().size() == 1);
 258 
 259         ClassLoader loader = layer.findLoader("pearscript");
 260         ScriptEngineFactory factory;
 261 
 262         // load using the class loader as context
 263         factory = ServiceLoader.load(ScriptEngineFactory.class, loader)
 264                 .findFirst()
 265                 .orElse(null);
 266         assertNotNull(factory);
 267         assertTrue(factory.getClass().getClassLoader() == loader);
 268 
 269         // load using the layer as context
 270         factory = ServiceLoader.load(layer, ScriptEngineFactory.class)
 271                 .findFirst()
 272                 .orElse(null);
 273         assertNotNull(factory);
 274         assertTrue(factory.getClass().getClassLoader() == loader);
 275     }
 276 
 277     /**
 278      * Basic test of ServiceLoader.load, using the class loader for
 279      * a module in a custom layer as the context.
 280      */
 281     @Test
 282     public void testWithCustomLayer1() {
 283         ModuleLayer layer = createCustomLayer("bananascript");
 284 
 285         ClassLoader loader = layer.findLoader("bananascript");
 286         List<ScriptEngineFactory> providers
 287             = collectAll(ServiceLoader.load(ScriptEngineFactory.class, loader));
 288 
 289         // should have at least 2 x bananascript + pearscript
 290         assertTrue(providers.size() >= 3);
 291 
 292         // first element should be the provider in the custom layer
 293         ScriptEngineFactory factory = providers.get(0);
 294         assertTrue(factory.getClass().getClassLoader() == loader);
 295         assertTrue(factory.getClass().getModule().getLayer() == layer);
 296         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 297 
 298         // remainder should be the boot layer
 299         providers.remove(0);
 300         Set<String> names = providers.stream()
 301                 .map(ScriptEngineFactory::getEngineName)
 302                 .collect(Collectors.toSet());
 303         assertTrue(names.contains("BananaScriptEngine"));
 304         assertTrue(names.contains("PearScriptEngine"));
 305     }
 306 
 307     /**
 308      * Basic test of ServiceLoader.load using a custom Layer as the context.
 309      */
 310     @Test
 311     public void testWithCustomLayer2() {
 312         ModuleLayer layer = createCustomLayer("bananascript");
 313 
 314         List<ScriptEngineFactory> factories
 315             = collectAll(ServiceLoader.load(layer, ScriptEngineFactory.class));
 316 
 317         // should have at least 2 x bananascript
 318         assertTrue(factories.size() >= 2);
 319 
 320         // first element should be the provider in the custom layer
 321         ScriptEngineFactory factory = factories.get(0);
 322         assertTrue(factory.getClass().getModule().getLayer() == layer);
 323         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 324 
 325         // remainder should be the boot layer
 326         factories.remove(0);
 327         Set<String> names = factories.stream()
 328                 .map(ScriptEngineFactory::getEngineName)
 329                 .collect(Collectors.toSet());
 330         assertTrue(names.contains("BananaScriptEngine"));
 331         assertFalse(names.contains("PearScriptEngine"));
 332     }
 333 
 334     /**
 335      * Basic test of ServiceLoader.load with a tree of layers.
 336      *
 337      * Test scenario:
 338      * - boot layer contains "bananascript", maybe other script engines
 339      * - layer1, with boot layer as parent, contains "bananascript"
 340      * - layer2, with boot layer as parent, contains "bananascript"
 341      * - layer3, with layer1 ad layer as parents, contains "bananascript"
 342      *
 343      * ServiceLoader should locate all 4 script engine factories in DFS order.
 344      */
 345     @Test
 346     public void testWithCustomLayer3() {
 347         ModuleLayer bootLayer = ModuleLayer.boot();
 348         Configuration cf0 = bootLayer.configuration();
 349 
 350         // boot layer should contain "bananascript"
 351         List<ScriptEngineFactory> factories
 352             = collectAll(ServiceLoader.load(bootLayer, ScriptEngineFactory.class));
 353         int countInBootLayer = factories.size();
 354         assertTrue(countInBootLayer >= 1);
 355         assertTrue(factories.stream()
 356                 .map(p -> p.getEngineName())
 357                 .filter("BananaScriptEngine"::equals)
 358                 .findAny()
 359                 .isPresent());
 360 
 361         ClassLoader scl = ClassLoader.getSystemClassLoader();
 362         ModuleFinder finder = ModuleFinder.of(testModulePath());
 363 
 364         // layer1
 365         Configuration cf1 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 366         ModuleLayer layer1 = bootLayer.defineModulesWithOneLoader(cf1, scl);
 367         assertTrue(layer1.modules().size() == 1);
 368 
 369         // layer2
 370         Configuration cf2 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
 371         ModuleLayer layer2 = bootLayer.defineModulesWithOneLoader(cf2, scl);
 372         assertTrue(layer2.modules().size() == 1);
 373 
 374         // layer3 with layer1 and layer2 as parents
 375         Configuration cf3 = Configuration.resolveAndBind(finder,
 376                 List.of(cf1, cf2),
 377                 ModuleFinder.of(),
 378                 Set.of());
 379         ModuleLayer layer3
 380             = ModuleLayer.defineModulesWithOneLoader(cf3, List.of(layer1, layer2), scl).layer();
 381         assertTrue(layer3.modules().size() == 1);
 382 
 383 
 384         // class loaders
 385         ClassLoader loader1 = layer1.findLoader("bananascript");
 386         ClassLoader loader2 = layer2.findLoader("bananascript");
 387         ClassLoader loader3 = layer3.findLoader("bananascript");
 388         assertTrue(loader1 != loader2);
 389         assertTrue(loader1 != loader3);
 390         assertTrue(loader2 != loader3);
 391 
 392         // load all factories with layer3 as the context
 393         factories = collectAll(ServiceLoader.load(layer3, ScriptEngineFactory.class));
 394         int count = factories.size();
 395         assertTrue(count == countInBootLayer + 3);
 396 
 397         // the ordering should be layer3, layer1, boot layer, layer2
 398 
 399         ScriptEngineFactory factory = factories.get(0);
 400         assertTrue(factory.getClass().getModule().getLayer() == layer3);
 401         assertTrue(factory.getClass().getClassLoader() == loader3);
 402         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 403 
 404         factory = factories.get(1);
 405         assertTrue(factory.getClass().getModule().getLayer() == layer1);
 406         assertTrue(factory.getClass().getClassLoader() == loader1);
 407         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 408 
 409         // boot layer "bananascript" and maybe other factories
 410         int last = count -1;
 411         boolean found = false;
 412         for (int i=2; i<last; i++) {
 413             factory = factories.get(i);
 414             assertTrue(factory.getClass().getModule().getLayer() == bootLayer);
 415             if (factory.getEngineName().equals("BananaScriptEngine")) {
 416                 assertFalse(found);
 417                 found = true;
 418             }
 419         }
 420         assertTrue(found);
 421 
 422         factory = factories.get(last);
 423         assertTrue(factory.getClass().getModule().getLayer() == layer2);
 424         assertTrue(factory.getClass().getClassLoader() == loader2);
 425         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 426     }
 427 
 428 
 429     // -- nulls --
 430 
 431     @Test(expectedExceptions = { NullPointerException.class })
 432     public void testLoadNull1() {
 433         ServiceLoader.load(null);
 434     }
 435 
 436     @Test(expectedExceptions = { NullPointerException.class })
 437     public void testLoadNull2() {
 438         ServiceLoader.load((Class<?>) null, ClassLoader.getSystemClassLoader());
 439     }
 440 
 441     @Test(expectedExceptions = { NullPointerException.class })
 442     public void testLoadNull3() {
 443         class S { }
 444         ServiceLoader.load((ModuleLayer) null, S.class);
 445     }
 446 
 447     @Test(expectedExceptions = { NullPointerException.class })
 448     public void testLoadNull4() {
 449         ServiceLoader.load(ModuleLayer.empty(), null);
 450     }
 451 
 452     @Test(expectedExceptions = { NullPointerException.class })
 453     public void testLoadNull5() {
 454         ServiceLoader.loadInstalled(null);
 455     }
 456 
 457     /**
 458      * Create a custom layer by resolving the given module names. The modules
 459      * are located on the test module path ({@code ${test.module.path}}).
 460      */
 461     private ModuleLayer createCustomLayer(String... modules) {
 462         ModuleFinder finder = ModuleFinder.of(testModulePath());
 463         Set<String> roots = new HashSet<>();
 464         Collections.addAll(roots, modules);
 465         ModuleLayer bootLayer = ModuleLayer.boot();
 466         Configuration parent = bootLayer.configuration();
 467         Configuration cf = parent.resolve(finder, ModuleFinder.of(), roots);
 468         ClassLoader scl = ClassLoader.getSystemClassLoader();
 469         ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 470         assertTrue(layer.modules().size() == 1);
 471         return layer;
 472     }
 473 
 474     private Path[] testModulePath() {
 475         String mp = System.getProperty("test.module.path");
 476         return Stream.of(mp.split(File.pathSeparator))
 477                 .map(Paths::get)
 478                 .toArray(Path[]::new);
 479     }
 480 
 481     private <E> List<E> collectAll(ServiceLoader<E> loader) {
 482         List<E> list = new ArrayList<>();
 483         Iterator<E> iterator = loader.iterator();
 484         while (iterator.hasNext()) {
 485             list.add(iterator.next());
 486         }
 487         return list;
 488     }
 489 }
 490