ソースを参照

Make ProviderLocator aware of boot qualified exports (#105250)

Qualfied exports in the boot layer only work when they are to other boot
modules. Yet Elasticsearch has dynamically loaded modules as in plugins.
For this purpose we have ModuleQualifiedExportsService. This commit
moves loading of ModuleQualfiedExportService instances in the boot layer
into core so that it can be reused by ProviderLocator when a qualified
export applies to an embedded module.
Ryan Ernst 1 年間 前
コミット
2ca6df71d6

+ 1 - 0
libs/core/build.gradle

@@ -12,6 +12,7 @@ apply plugin: 'elasticsearch.mrjar'
 dependencies {
   // This dependency is used only by :libs:core for null-checking interop with other tools
   compileOnly "com.google.code.findbugs:jsr305:3.0.2"
+  compileOnly project(':libs:elasticsearch-logging')
 
   testImplementation "com.carrotsearch.randomizedtesting:randomizedtesting-runner:${versions.randomizedrunner}"
   testImplementation "junit:junit:${versions.junit}"

+ 5 - 0
libs/core/src/main/java/module-info.java

@@ -6,10 +6,15 @@
  * Side Public License, v 1.
  */
 
+import org.elasticsearch.jdk.ModuleQualifiedExportsService;
+
 module org.elasticsearch.base {
     requires static jsr305;
+    requires org.elasticsearch.logging;
 
     exports org.elasticsearch.core;
     exports org.elasticsearch.jdk;
     exports org.elasticsearch.core.internal.provider to org.elasticsearch.xcontent;
+
+    uses ModuleQualifiedExportsService;
 }

+ 8 - 0
libs/core/src/main/java/org/elasticsearch/core/internal/provider/ProviderLocator.java

@@ -8,6 +8,8 @@
 
 package org.elasticsearch.core.internal.provider;
 
+import org.elasticsearch.jdk.ModuleQualifiedExportsService;
+
 import java.io.IOException;
 import java.io.UncheckedIOException;
 import java.lang.module.Configuration;
@@ -22,6 +24,8 @@ import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.function.Supplier;
 
+import static org.elasticsearch.jdk.ModuleQualifiedExportsService.exposeQualifiedExportsAndOpens;
+
 /**
  * A provider locator that finds the implementation of the specified provider.
  *
@@ -120,6 +124,10 @@ public final class ProviderLocator<T> implements Supplier<T> {
         ModuleLayer parentLayer = ModuleLayer.boot();
         Configuration cf = parentLayer.configuration().resolve(ModuleFinder.of(), moduleFinder, Set.of(providerModuleName));
         ModuleLayer layer = parentLayer.defineModules(cf, nm -> loader); // all modules in one loader
+        // check each module for boot modules that have qualified exports/opens to it
+        for (Module m : layer.modules()) {
+            exposeQualifiedExportsAndOpens(m, ModuleQualifiedExportsService.getBootServices());
+        }
         ServiceLoader<T> sl = ServiceLoader.load(layer, providerType);
         return sl.findFirst().orElseThrow(newIllegalStateException(providerName));
     }

+ 63 - 5
libs/core/src/main/java/org/elasticsearch/jdk/ModuleQualifiedExportsService.java

@@ -8,6 +8,9 @@
 
 package org.elasticsearch.jdk;
 
+import org.elasticsearch.logging.LogManager;
+import org.elasticsearch.logging.Logger;
+
 import java.lang.module.ModuleDescriptor.Exports;
 import java.lang.module.ModuleDescriptor.Opens;
 import java.util.ArrayList;
@@ -15,6 +18,7 @@ import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -24,14 +28,68 @@ import java.util.stream.Stream;
 /**
  * An object that provides a callback for qualified exports and opens.
  *
- * Because Elasticsearch constructs plugin module layers dynamically, qualified
- * exports are silently dropped in the boot layer. Modules that have qualified
- * exports should implement this service so that when a qualified export
- * module is loaded, the exporting or opening module can be informed and
- * export or open dynamically to the newly loaded module.
+ * Because Elasticsearch sometimes constructs module layers dynamically
+ * (eg for plugins), qualified exports are silently dropped if a target
+ * module does not yet exist. Modules that have qualified exports to
+ * other dynamically created modules should implement this service so
+ * that when a qualified export module is loaded, the exporting or
+ * opening module can be informed and export or open dynamically to
+ * the newly loaded module.
  */
 public abstract class ModuleQualifiedExportsService {
 
+    private static final Logger logger = LogManager.getLogger(ModuleQualifiedExportsService.class);
+
+    // holds instances of ModuleQualfiedExportsService that exist in the boot layer
+    private static class Holder {
+        private static final Map<String, List<ModuleQualifiedExportsService>> exportsServices;
+
+        static {
+            Map<String, List<ModuleQualifiedExportsService>> qualifiedExports = new HashMap<>();
+            var loader = ServiceLoader.load(ModuleQualifiedExportsService.class, ModuleQualifiedExportsService.class.getClassLoader());
+            for (var exportsService : loader) {
+                addExportsService(qualifiedExports, exportsService, exportsService.getClass().getModule().getName());
+            }
+            exportsServices = Map.copyOf(qualifiedExports);
+        }
+    }
+
+    /**
+     * A utility method to add an export service to the given map of exports services.
+     *
+     * The map is inverted, keyed by the target module name to which an exports/opens applies.
+     *
+     * @param qualifiedExports A map of modules to which qualfied exports need to be applied
+     * @param exportsService The exports service to add to the map
+     * @param moduleName The name of the module that is doing the exporting
+     */
+    public static void addExportsService(
+        Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
+        ModuleQualifiedExportsService exportsService,
+        String moduleName
+    ) {
+        for (String targetName : exportsService.getTargets()) {
+            logger.debug("Registered qualified export from module " + moduleName + " to " + targetName);
+            qualifiedExports.computeIfAbsent(targetName, k -> new ArrayList<>()).add(exportsService);
+        }
+    }
+
+    /**
+     * Adds qualified exports and opens declared in other upstream modules to the target module.
+     * This is required since qualified statements targeting yet-to-be-created modules, i.e. plugins,
+     * are silently dropped when the boot layer is created.
+     */
+    public static void exposeQualifiedExportsAndOpens(Module target, Map<String, List<ModuleQualifiedExportsService>> qualifiedExports) {
+        qualifiedExports.getOrDefault(target.getName(), List.of()).forEach(exportService -> exportService.addExportsAndOpens(target));
+    }
+
+    /**
+     * Returns a mapping of ModuleQualifiedExportsServices that exist in the boot layer.
+     */
+    public static Map<String, List<ModuleQualifiedExportsService>> getBootServices() {
+        return Holder.exportsServices;
+    }
+
     protected final Module module;
     private final Map<String, List<String>> qualifiedExports;
     private final Map<String, List<String>> qualifiedOpens;

+ 3 - 32
server/src/main/java/org/elasticsearch/plugins/PluginsService.java

@@ -63,6 +63,8 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static org.elasticsearch.common.io.FileSystemUtils.isAccessibleDirectory;
+import static org.elasticsearch.jdk.ModuleQualifiedExportsService.addExportsService;
+import static org.elasticsearch.jdk.ModuleQualifiedExportsService.exposeQualifiedExportsAndOpens;
 
 public class PluginsService implements ReportingService<PluginsAndModules> {
 
@@ -98,17 +100,6 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
     private static final Logger logger = LogManager.getLogger(PluginsService.class);
     private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(PluginsService.class);
 
-    private static final Map<String, List<ModuleQualifiedExportsService>> exportsServices;
-
-    static {
-        Map<String, List<ModuleQualifiedExportsService>> qualifiedExports = new HashMap<>();
-        var loader = ServiceLoader.load(ModuleQualifiedExportsService.class, PluginsService.class.getClassLoader());
-        for (var exportsService : loader) {
-            addExportsService(qualifiedExports, exportsService, exportsService.getClass().getModule().getName());
-        }
-        exportsServices = Map.copyOf(qualifiedExports);
-    }
-
     private final Settings settings;
     private final Path configPath;
 
@@ -134,7 +125,7 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
         this.settings = settings;
         this.configPath = configPath;
 
-        Map<String, List<ModuleQualifiedExportsService>> qualifiedExports = new HashMap<>(exportsServices);
+        Map<String, List<ModuleQualifiedExportsService>> qualifiedExports = new HashMap<>(ModuleQualifiedExportsService.getBootServices());
         addServerExportsService(qualifiedExports);
 
         Set<PluginBundle> seenBundles = new LinkedHashSet<>();
@@ -801,26 +792,6 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
         }
     }
 
-    /**
-     * Adds qualified exports and opens declared in other upstream modules to the target module.
-     * This is required since qualified statements targeting yet-to-be-created modules, i.e. plugins,
-     * are silently dropped when the boot layer is created.
-     */
-    private static void exposeQualifiedExportsAndOpens(Module target, Map<String, List<ModuleQualifiedExportsService>> qualifiedExports) {
-        qualifiedExports.getOrDefault(target.getName(), List.of()).forEach(exportService -> exportService.addExportsAndOpens(target));
-    }
-
-    private static void addExportsService(
-        Map<String, List<ModuleQualifiedExportsService>> qualifiedExports,
-        ModuleQualifiedExportsService exportsService,
-        String moduleName
-    ) {
-        for (String targetName : exportsService.getTargets()) {
-            logger.debug("Registered qualified export from module " + moduleName + " to " + targetName);
-            qualifiedExports.computeIfAbsent(targetName, k -> new ArrayList<>()).add(exportsService);
-        }
-    }
-
     protected void addServerExportsService(Map<String, List<ModuleQualifiedExportsService>> qualifiedExports) {
         final Module serverModule = PluginsService.class.getModule();
         var exportsService = new ModuleQualifiedExportsService(serverModule) {