From 11a5807682080cf26a333d515cee25dcd1b4fc93 Mon Sep 17 00:00:00 2001 From: John Niang Date: Wed, 19 Apr 2023 18:42:25 +0800 Subject: [PATCH] Enable defining Reconciler in plugin (#3789) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /area core /area plugin #### What this PR does / why we need it: This PR adds reconciliation mechanism for plugin. After that, we could define a `Reconciler` like the following when developing plugin: ```java @Slf4j @Component public class ApplicationReconciler implements Reconciler { @Override public Result reconcile(Request request) { log.info("Application {} changed.", request); return Result.doNotRetry(); } @Override public Controller setupWith(ControllerBuilder builder) { return builder .extension(new Application()) .workerCount(1) .build(); } } ``` You can reconcile any extensions as needed. Meanwhile, all plugins will be stopped when Halo is shutting down. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3783 #### Does this PR introduce a user-facing change? ```release-note 支持在插件中定义 Reconciler ``` --- .../halo/app/plugin/HaloPluginManager.java | 8 ++- .../app/plugin/PluginControllerManager.java | 58 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 application/src/main/java/run/halo/app/plugin/PluginControllerManager.java diff --git a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java index 1c384437b..1f717d7a9 100644 --- a/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java +++ b/application/src/main/java/run/halo/app/plugin/HaloPluginManager.java @@ -21,6 +21,7 @@ import org.pf4j.PluginState; import org.pf4j.PluginStateEvent; import org.pf4j.PluginWrapper; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -41,7 +42,7 @@ import run.halo.app.plugin.event.HaloPluginStoppedEvent; */ @Slf4j public class HaloPluginManager extends DefaultPluginManager - implements ApplicationContextAware, InitializingBean { + implements ApplicationContextAware, InitializingBean, DisposableBean { private final Map startingErrors = new HashMap<>(); @@ -403,5 +404,10 @@ public class HaloPluginManager extends DefaultPluginManager springComponentsFinder.removeComponentsStorage(pluginId); } } + + @Override + public void destroy() throws Exception { + stopPlugins(); + } // end-region } diff --git a/application/src/main/java/run/halo/app/plugin/PluginControllerManager.java b/application/src/main/java/run/halo/app/plugin/PluginControllerManager.java new file mode 100644 index 000000000..c50bef688 --- /dev/null +++ b/application/src/main/java/run/halo/app/plugin/PluginControllerManager.java @@ -0,0 +1,58 @@ +package run.halo.app.plugin; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import org.springframework.context.event.EventListener; +import org.springframework.core.ResolvableType; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.controller.ControllerManager; +import run.halo.app.extension.controller.DefaultControllerManager; +import run.halo.app.extension.controller.Reconciler; +import run.halo.app.extension.controller.Reconciler.Request; +import run.halo.app.plugin.event.HaloPluginBeforeStopEvent; +import run.halo.app.plugin.event.HaloPluginStartedEvent; + +@Component +public class PluginControllerManager { + + private final Map controllerManagerMap; + + private final ExtensionClient client; + + public PluginControllerManager(ExtensionClient client) { + this.client = client; + controllerManagerMap = new ConcurrentHashMap<>(); + } + + @EventListener + public void onPluginStarted(HaloPluginStartedEvent event) { + var plugin = event.getPlugin(); + + var controllerManager = controllerManagerMap.computeIfAbsent(plugin.getPluginId(), + id -> new DefaultControllerManager(client)); + + getReconcilers(plugin.getPluginId()) + .forEach(controllerManager::start); + } + + @EventListener + public void onPluginBeforeStop(HaloPluginBeforeStopEvent event) { + // remove controller manager + var plugin = event.getPlugin(); + var controllerManager = controllerManagerMap.remove(plugin.getPluginId()); + if (controllerManager != null) { + // stop all reconcilers + getReconcilers(plugin.getPluginId()) + .forEach(controllerManager::stop); + } + } + + private Stream> getReconcilers(String pluginId) { + var context = ExtensionContextRegistry.getInstance().getByPluginId(pluginId); + return context.>getBeanProvider( + ResolvableType.forClassWithGenerics(Reconciler.class, Request.class)) + .orderedStream(); + } +}