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