فهرست منبع

Load stable plugins as synthetic modules (#91869)

* Load nonmodular stable plugins as ubermodules

We can use the ubermodule classloader to load stable plugins if the
plugin descriptor does not give us a module to load from the plugin
bundle.

We create the synthetic module name by munging the plugin name into a
suitable form.

For testing, we have to provide the uber module classloader read access to the app classloader's unnamed module, where the stable api modules are loaded by default.

* Update docs/changelog/91869.yaml
William Brafford 2 سال پیش
والد
کامیت
788750b748

+ 5 - 0
docs/changelog/91869.yaml

@@ -0,0 +1,5 @@
+pr: 91869
+summary: Load stable plugins as synthetic modules
+area: Infra/Plugins
+type: enhancement
+issues: []

+ 21 - 0
server/src/main/java/org/elasticsearch/plugins/PluginsService.java

@@ -523,12 +523,33 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
                 extendedPlugins.stream().map(LoadedPlugin::layer)
             ).toList();
             return createPluginModuleLayer(bundle, pluginParentLoader, parentLayers);
+        } else if (plugin.isStable()) {
+            logger.debug(() -> "Loading bundle: " + plugin.getName() + ", non-modular as synthetic module");
+            return LayerAndLoader.ofLoader(
+                UberModuleClassLoader.getInstance(
+                    pluginParentLoader,
+                    ModuleLayer.boot(),
+                    "synthetic." + toModuleName(plugin.getName()),
+                    bundle.allUrls,
+                    Set.of("org.elasticsearch.server") // TODO: instead of denying server, allow only jvm + stable API modules
+                )
+            );
         } else {
             logger.debug(() -> "Loading bundle: " + plugin.getName() + ", non-modular");
             return LayerAndLoader.ofLoader(URLClassLoader.newInstance(bundle.urls.toArray(URL[]::new), pluginParentLoader));
         }
     }
 
+    // package-visible for testing
+    static String toModuleName(String name) {
+        String result = name.replaceAll("\\W+", ".") // replace non-alphanumeric character strings with dots
+            .replaceAll("(^[^A-Za-z_]*)", "") // trim non-alpha or underscore characters from start
+            .replaceAll("\\.$", "") // trim trailing dot
+            .toLowerCase(Locale.getDefault());
+        assert ModuleSupport.isPackageName(result);
+        return result;
+    }
+
     private static void checkDeprecations(
         PluginIntrospector inspector,
         List<PluginDescriptor> pluginDescriptors,

+ 7 - 0
server/src/main/java/org/elasticsearch/plugins/UberModuleClassLoader.java

@@ -280,6 +280,13 @@ public class UberModuleClassLoader extends SecureClassLoader implements AutoClos
         }
     }
 
+    // For testing in cases where code must be given access to an unnamed module
+    void addReadsSystemClassLoaderUnnamedModule() {
+        moduleController.layer()
+            .modules()
+            .forEach(module -> moduleController.addReads(module, ClassLoader.getSystemClassLoader().getUnnamedModule()));
+    }
+
     /**
      * Returns the package name for the given class name
      */

+ 33 - 0
server/src/test/java/org/elasticsearch/plugins/PluginsServiceTests.java

@@ -17,6 +17,8 @@ import org.elasticsearch.core.Strings;
 import org.elasticsearch.env.Environment;
 import org.elasticsearch.env.TestEnvironment;
 import org.elasticsearch.index.IndexModule;
+import org.elasticsearch.plugin.analysis.api.CharFilterFactory;
+import org.elasticsearch.plugins.scanners.PluginInfo;
 import org.elasticsearch.plugins.spi.BarPlugin;
 import org.elasticsearch.plugins.spi.BarTestService;
 import org.elasticsearch.plugins.spi.TestService;
@@ -823,6 +825,23 @@ public class PluginsServiceTests extends ESTestCase {
             assertThat(pluginInfos.get(0).descriptor().getName(), equalTo("stable-plugin"));
             assertThat(pluginInfos.get(0).descriptor().isStable(), is(true));
 
+            // check ubermodule classloader usage
+            Collection<PluginInfo> stablePluginInfos = pluginService.getStablePluginRegistry()
+                .getPluginInfosForExtensible("org.elasticsearch.plugin.analysis.api.CharFilterFactory");
+            assertThat(stablePluginInfos, hasSize(1));
+            ClassLoader stablePluginClassLoader = stablePluginInfos.stream().findFirst().orElseThrow().loader();
+            assertThat(stablePluginClassLoader, instanceOf(UberModuleClassLoader.class));
+
+            if (CharFilterFactory.class.getModule().isNamed() == false) {
+                // test frameworks run with stable api classes on classpath, so we
+                // have no choice but to let our class read the unnamed module that
+                // owns the stable api classes
+                ((UberModuleClassLoader) stablePluginClassLoader).addReadsSystemClassLoaderUnnamedModule();
+            }
+
+            Class<?> stableClass = stablePluginClassLoader.loadClass("p.A");
+            assertThat(stableClass.getModule().getName(), equalTo("synthetic.stable.plugin"));
+
             // TODO should we add something to pluginInfos.get(0).pluginApiInfo() ?
         } finally {
             closePluginLoaders(pluginService);
@@ -838,6 +857,20 @@ public class PluginsServiceTests extends ESTestCase {
         assertEquals(this.getClass().getClassLoader(), loader.getParent());
     }
 
+    public void testToModuleName() {
+        assertThat(PluginsService.toModuleName("module.name"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("module-name"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("module-name1"), equalTo("module.name1"));
+        assertThat(PluginsService.toModuleName("1module-name"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("module-name!"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("module!@#name!"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("!module-name!"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("module_name"), equalTo("module_name"));
+        assertThat(PluginsService.toModuleName("-module-name-"), equalTo("module.name"));
+        assertThat(PluginsService.toModuleName("_module_name"), equalTo("_module_name"));
+        assertThat(PluginsService.toModuleName("_"), equalTo("_"));
+    }
+
     static final class Loader extends ClassLoader {
         Loader(ClassLoader parent) {
             super(parent);