Enable defining Reconciler in plugin (#3789)

#### 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<Request>` like the following when developing plugin:

```java
@Slf4j
@Component
public class ApplicationReconciler implements Reconciler<Reconciler.Request> {

    @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
```
pull/3744/head
John Niang 2023-04-19 18:42:25 +08:00 committed by GitHub
parent 7afb3b8687
commit 11a5807682
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 1 deletions

View File

@ -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<String, PluginStartingError> startingErrors = new HashMap<>();
@ -403,5 +404,10 @@ public class HaloPluginManager extends DefaultPluginManager
springComponentsFinder.removeComponentsStorage(pluginId);
}
}
@Override
public void destroy() throws Exception {
stopPlugins();
}
// end-region
}

View File

@ -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<String, ControllerManager> 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<Reconciler<Request>> getReconcilers(String pluginId) {
var context = ExtensionContextRegistry.getInstance().getByPluginId(pluginId);
return context.<Reconciler<Request>>getBeanProvider(
ResolvableType.forClassWithGenerics(Reconciler.class, Request.class))
.orderedStream();
}
}