diff --git a/server/src/main/java/org/opensearch/plugins/PluginsService.java b/server/src/main/java/org/opensearch/plugins/PluginsService.java index 6c06e00c5b04f..2789c8b11a355 100644 --- a/server/src/main/java/org/opensearch/plugins/PluginsService.java +++ b/server/src/main/java/org/opensearch/plugins/PluginsService.java @@ -605,12 +605,13 @@ private static void addSortedBundle( private List> loadBundles(Set bundles) { List> plugins = new ArrayList<>(); Map loaded = new HashMap<>(); + Map loadedInfos = new HashMap<>(); Map> transitiveUrls = new HashMap<>(); List sortedBundles = sortBundles(bundles); for (Bundle bundle : sortedBundles) { checkBundleJarHell(JarHell.parseClassPath(), bundle, transitiveUrls); - final Plugin plugin = loadBundle(bundle, loaded); + final Plugin plugin = loadBundle(bundle, loaded, loadedInfos); plugins.add(new Tuple<>(bundle.plugin, plugin)); } @@ -619,9 +620,14 @@ private List> loadBundles(Set bundles) { // package-private for test visibility static void loadExtensions(List> plugins) { - Map> extendingPluginsByName = plugins.stream() - .flatMap(t -> t.v1().getExtendedPlugins().stream().map(extendedPlugin -> Tuple.tuple(extendedPlugin, t.v2()))) - .collect(Collectors.groupingBy(Tuple::v1, Collectors.mapping(Tuple::v2, Collectors.toList()))); + Map pluginInfoMap = plugins.stream().collect(Collectors.toMap(t -> t.v1().getName(), Tuple::v1)); + Map> extendingPluginsByName = new HashMap<>(); + + for (Tuple pluginTuple : plugins) { + Set visited = new HashSet<>(); + registerWithAncestors(pluginTuple.v1(), pluginTuple.v2(), pluginInfoMap, extendingPluginsByName, visited); + } + for (Tuple pluginTuple : plugins) { if (pluginTuple.v2() instanceof ExtensiblePlugin extensiblePlugin) { loadExtensionsForPlugin( @@ -632,6 +638,28 @@ static void loadExtensions(List> plugins) { } } + private static void registerWithAncestors( + PluginInfo pluginInfo, + Plugin plugin, + Map pluginInfoMap, + Map> extendingPluginsByName, + Set visited + ) { + for (String extendedPluginName : pluginInfo.getExtendedPlugins()) { + if (visited.contains(extendedPluginName)) { + continue; + } + visited.add(extendedPluginName); + + extendingPluginsByName.computeIfAbsent(extendedPluginName, k -> new ArrayList<>()).add(plugin); + + PluginInfo extendedPluginInfo = pluginInfoMap.get(extendedPluginName); + if (extendedPluginInfo != null) { + registerWithAncestors(extendedPluginInfo, plugin, pluginInfoMap, extendingPluginsByName, visited); + } + } + } + private static void loadExtensionsForPlugin(ExtensiblePlugin extensiblePlugin, List extendingPlugins) { ExtensiblePlugin.ExtensionLoader extensionLoader = new ExtensiblePlugin.ExtensionLoader() { @Override @@ -765,24 +793,16 @@ static void checkBundleJarHell(Set classpath, Bundle bundle, Map loaded) { + private Plugin loadBundle(Bundle bundle, Map loaded, Map loadedInfos) { String name = bundle.plugin.getName(); verifyCompatibility(bundle.plugin); - // collect loaders of extended plugins + // collect loaders of extended plugins and their transitive dependencies List extendedLoaders = new ArrayList<>(); + Set visited = new HashSet<>(); for (String extendedPluginName : bundle.plugin.getExtendedPlugins()) { - Plugin extendedPlugin = loaded.get(extendedPluginName); - if (extendedPlugin == null && bundle.plugin.isExtendedPluginOptional(extendedPluginName)) { - // extended plugin is optional and is not installed - continue; - } - assert extendedPlugin != null; - if (ExtensiblePlugin.class.isInstance(extendedPlugin) == false) { - throw new IllegalStateException("Plugin [" + name + "] cannot extend non-extensible plugin [" + extendedPluginName + "]"); - } - extendedLoaders.add(extendedPlugin.getClass().getClassLoader()); + collectTransitiveClassLoaders(extendedPluginName, loaded, loadedInfos, bundle.plugin, extendedLoaders, visited); } // create a child to load the plugin in this bundle @@ -814,6 +834,7 @@ private Plugin loadBundle(Bundle bundle, Map loaded) { } Plugin plugin = loadPlugin(pluginClass, settings, configPath); loaded.put(name, plugin); + loadedInfos.put(name, bundle.plugin); return plugin; } finally { Thread.currentThread().setContextClassLoader(cl); @@ -885,6 +906,41 @@ private String signatureMessage(final Class clazz) { ); } + private void collectTransitiveClassLoaders( + String pluginName, + Map loaded, + Map loadedInfos, + PluginInfo currentPlugin, + List loaders, + Set visited + ) { + if (visited.contains(pluginName)) { + return; + } + visited.add(pluginName); + + Plugin plugin = loaded.get(pluginName); + if (plugin == null && currentPlugin.isExtendedPluginOptional(pluginName)) { + return; + } + assert plugin != null; + if (ExtensiblePlugin.class.isInstance(plugin) == false) { + throw new IllegalStateException( + "Plugin [" + currentPlugin.getName() + "] cannot extend non-extensible plugin [" + pluginName + "]" + ); + } + + // Recursively collect classloaders from plugins that this plugin extends + PluginInfo pluginInfo = loadedInfos.get(pluginName); + if (pluginInfo != null) { + for (String extendedPluginName : pluginInfo.getExtendedPlugins()) { + collectTransitiveClassLoaders(extendedPluginName, loaded, loadedInfos, pluginInfo, loaders, visited); + } + } + + loaders.add(plugin.getClass().getClassLoader()); + } + public List filterPlugins(Class type) { return plugins.stream().filter(x -> type.isAssignableFrom(x.v2().getClass())).map(p -> ((T) p.v2())).collect(Collectors.toList()); }