From d355e797bdfbd6551040716ff4606fdea3419bf7 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Thu, 30 Mar 2023 16:38:13 +0800 Subject: [PATCH] feat: add reactive setting fetcher for plugin (#3625) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /milestone 2.4.x /area core #### What this PR does / why we need it: 提供 ReactiveSettingFetcher 供插件获取配置 此 PR 基于原有的阻塞的 SettingFetcher 逻辑挪到 DefaultReactiveSettingFetcher 中并将阻塞的实现用 Reactive 得代理,不需要测试,单元测试过了即可。 可以尝试在插件中依赖注入 ReactiveSettingFetcher 看是否能正确注入 #### Which issue(s) this PR fixes: Fixes #3620 #### Does this PR introduce a user-facing change? ```release-note 提供 ReactiveSettingFetcher 供插件获取配置 ``` --- .../app/plugin/ReactiveSettingFetcher.java | 23 +++++ .../plugin/DefaultReactiveSettingFetcher.java | 99 +++++++++++++++++++ .../app/plugin/DefaultSettingFetcher.java | 69 ++----------- .../plugin/PluginApplicationInitializer.java | 13 ++- .../app/plugin/DefaultSettingFetcherTest.java | 14 ++- 5 files changed, 147 insertions(+), 71 deletions(-) create mode 100644 api/src/main/java/run/halo/app/plugin/ReactiveSettingFetcher.java create mode 100644 application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java diff --git a/api/src/main/java/run/halo/app/plugin/ReactiveSettingFetcher.java b/api/src/main/java/run/halo/app/plugin/ReactiveSettingFetcher.java new file mode 100644 index 000000000..8272fd646 --- /dev/null +++ b/api/src/main/java/run/halo/app/plugin/ReactiveSettingFetcher.java @@ -0,0 +1,23 @@ +package run.halo.app.plugin; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.Map; +import org.springframework.lang.NonNull; +import reactor.core.publisher.Mono; + +/** + * The {@link ReactiveSettingFetcher} to help plugin fetch own setting configuration. + * + * @author guqing + * @since 2.4.0 + */ +public interface ReactiveSettingFetcher { + + Mono fetch(String group, Class clazz); + + @NonNull + Mono get(String group); + + @NonNull + Mono> getValues(); +} diff --git a/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java b/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java new file mode 100644 index 000000000..dbb89efb9 --- /dev/null +++ b/application/src/main/java/run/halo/app/plugin/DefaultReactiveSettingFetcher.java @@ -0,0 +1,99 @@ +package run.halo.app.plugin; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.NonNull; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.Plugin; +import run.halo.app.extension.ConfigMap; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.infra.utils.JsonParseException; +import run.halo.app.infra.utils.JsonUtils; + +/** + * A default implementation of {@link ReactiveSettingFetcher}. + * + * @author guqing + * @since 2.0.0 + */ +public class DefaultReactiveSettingFetcher implements ReactiveSettingFetcher { + + private final ReactiveExtensionClient client; + + private final String pluginName; + + public DefaultReactiveSettingFetcher(ReactiveExtensionClient client, String pluginName) { + this.client = client; + this.pluginName = pluginName; + } + + @Override + public Mono fetch(String group, Class clazz) { + return getInternal(group) + .mapNotNull(jsonNode -> convertValue(jsonNode, clazz)); + } + + @Override + @NonNull + public Mono get(String group) { + return getInternal(group) + .switchIfEmpty( + Mono.error(new IllegalArgumentException("Group [" + group + "] does not exist.")) + ); + } + + @Override + @NonNull + public Mono> getValues() { + return getValuesInternal() + .map(Map::copyOf) + .defaultIfEmpty(Map.of()); + } + + private Mono getInternal(String group) { + return getValuesInternal() + .mapNotNull(values -> values.get(group)) + .defaultIfEmpty(JsonNodeFactory.instance.missingNode()); + } + + private Mono> getValuesInternal() { + return configMap(pluginName) + .mapNotNull(ConfigMap::getData) + .map(data -> { + Map result = new LinkedHashMap<>(); + data.forEach((key, value) -> result.put(key, readTree(value))); + return result; + }) + .defaultIfEmpty(Map.of()); + } + + private Mono configMap(String pluginName) { + return client.fetch(Plugin.class, pluginName) + .flatMap(plugin -> { + String configMapName = plugin.getSpec().getConfigMapName(); + if (StringUtils.isBlank(configMapName)) { + return Mono.empty(); + } + return client.fetch(ConfigMap.class, plugin.getSpec().getConfigMapName()); + }); + } + + private JsonNode readTree(String json) { + if (StringUtils.isBlank(json)) { + return JsonNodeFactory.instance.missingNode(); + } + try { + return JsonUtils.DEFAULT_JSON_MAPPER.readTree(json); + } catch (JsonProcessingException e) { + throw new JsonParseException(e); + } + } + + private T convertValue(JsonNode jsonNode, Class clazz) { + return JsonUtils.DEFAULT_JSON_MAPPER.convertValue(jsonNode, clazz); + } +} diff --git a/application/src/main/java/run/halo/app/plugin/DefaultSettingFetcher.java b/application/src/main/java/run/halo/app/plugin/DefaultSettingFetcher.java index 0437acdce..4a187634c 100644 --- a/application/src/main/java/run/halo/app/plugin/DefaultSettingFetcher.java +++ b/application/src/main/java/run/halo/app/plugin/DefaultSettingFetcher.java @@ -1,19 +1,11 @@ package run.halo.app.plugin; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import java.util.Collection; import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.springframework.lang.NonNull; -import run.halo.app.core.extension.Plugin; import run.halo.app.extension.ConfigMap; -import run.halo.app.extension.ExtensionClient; -import run.halo.app.infra.utils.JsonParseException; -import run.halo.app.infra.utils.JsonUtils; /** *

A value fetcher for plugin form configuration.

@@ -22,27 +14,23 @@ import run.halo.app.infra.utils.JsonUtils; * @since 2.0.0 */ public class DefaultSettingFetcher extends SettingFetcher { + private final ReactiveSettingFetcher delegateFetcher; - private final ExtensionClient extensionClient; - - private final String pluginName; - - public DefaultSettingFetcher(String pluginName, - ExtensionClient extensionClient) { - this.extensionClient = extensionClient; - this.pluginName = pluginName; + public DefaultSettingFetcher(ReactiveSettingFetcher reactiveSettingFetcher) { + this.delegateFetcher = reactiveSettingFetcher; } @NonNull @Override public Optional fetch(String group, Class clazz) { - return Optional.ofNullable(convertValue(getInternal(group), clazz)); + return delegateFetcher.fetch(group, clazz) + .blockOptional(); } @NonNull @Override public JsonNode get(String group) { - return getInternal(group); + return Objects.requireNonNull(delegateFetcher.get(group).block()); } /** @@ -53,47 +41,6 @@ public class DefaultSettingFetcher extends SettingFetcher { @NonNull @Override public Map getValues() { - return Map.copyOf(getValuesInternal()); - } - - private JsonNode getInternal(String group) { - return Optional.ofNullable(getValuesInternal().get(group)) - .orElse(JsonNodeFactory.instance.missingNode()); - } - - private Map getValuesInternal() { - return configMap(pluginName) - .filter(configMap -> configMap.getData() != null) - .map(ConfigMap::getData) - .map(Map::entrySet) - .stream() - .flatMap(Collection::stream) - .collect(Collectors.toMap(Map.Entry::getKey, entry -> readTree(entry.getValue()))); - } - - private Optional configMap(String pluginName) { - return extensionClient.fetch(Plugin.class, pluginName) - .flatMap(plugin -> { - String configMapName = plugin.getSpec().getConfigMapName(); - if (StringUtils.isBlank(configMapName)) { - return Optional.empty(); - } - return extensionClient.fetch(ConfigMap.class, plugin.getSpec().getConfigMapName()); - }); - } - - private JsonNode readTree(String json) { - if (StringUtils.isBlank(json)) { - return JsonNodeFactory.instance.missingNode(); - } - try { - return JsonUtils.DEFAULT_JSON_MAPPER.readTree(json); - } catch (JsonProcessingException e) { - throw new JsonParseException(e); - } - } - - private T convertValue(JsonNode jsonNode, Class clazz) { - return JsonUtils.DEFAULT_JSON_MAPPER.convertValue(jsonNode, clazz); + return Objects.requireNonNull(delegateFetcher.getValues().block()); } } diff --git a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java index 10b5edc26..ea3d825de 100644 --- a/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java +++ b/application/src/main/java/run/halo/app/plugin/PluginApplicationInitializer.java @@ -11,7 +11,7 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.lang.NonNull; import org.springframework.util.Assert; import org.springframework.util.StopWatch; -import run.halo.app.extension.ExtensionClient; +import run.halo.app.extension.ReactiveExtensionClient; /** * Plugin application initializer will create plugin application context by plugin id and @@ -111,10 +111,13 @@ public class PluginApplicationInitializer { private void populateSettingFetcher(String pluginName, DefaultListableBeanFactory listableBeanFactory) { - ExtensionClient extensionClient = - rootApplicationContext.getBean(ExtensionClient.class); - SettingFetcher settingFetcher = new DefaultSettingFetcher(pluginName, extensionClient); - listableBeanFactory.registerSingleton("settingFetcher", settingFetcher); + ReactiveExtensionClient extensionClient = + rootApplicationContext.getBean(ReactiveExtensionClient.class); + ReactiveSettingFetcher reactiveSettingFetcher = + new DefaultReactiveSettingFetcher(extensionClient, pluginName); + listableBeanFactory.registerSingleton("settingFetcher", + new DefaultSettingFetcher(reactiveSettingFetcher)); + listableBeanFactory.registerSingleton("reactiveSettingFetcher", reactiveSettingFetcher); } public void onStartUp(String pluginId) { diff --git a/application/src/test/java/run/halo/app/plugin/DefaultSettingFetcherTest.java b/application/src/test/java/run/halo/app/plugin/DefaultSettingFetcherTest.java index 52ec91c26..43365db89 100644 --- a/application/src/test/java/run/halo/app/plugin/DefaultSettingFetcherTest.java +++ b/application/src/test/java/run/halo/app/plugin/DefaultSettingFetcherTest.java @@ -18,10 +18,11 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.skyscreamer.jsonassert.JSONAssert; +import reactor.core.publisher.Mono; import run.halo.app.core.extension.Plugin; import run.halo.app.extension.ConfigMap; -import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.Metadata; +import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.infra.utils.JsonUtils; /** @@ -34,22 +35,24 @@ import run.halo.app.infra.utils.JsonUtils; class DefaultSettingFetcherTest { @Mock - private ExtensionClient extensionClient; + private ReactiveExtensionClient extensionClient; private DefaultSettingFetcher settingFetcher; @BeforeEach void setUp() { - settingFetcher = new DefaultSettingFetcher("fake", extensionClient); + DefaultReactiveSettingFetcher reactiveSettingFetcher = + new DefaultReactiveSettingFetcher(extensionClient, "fake"); + settingFetcher = new DefaultSettingFetcher(reactiveSettingFetcher); // do not call extensionClient when the settingFetcher first time created verify(extensionClient, times(0)).fetch(eq(ConfigMap.class), any()); verify(extensionClient, times(0)).fetch(eq(Plugin.class), any()); Plugin plugin = buildPlugin(); - when(extensionClient.fetch(eq(Plugin.class), any())).thenReturn(Optional.of(plugin)); + when(extensionClient.fetch(eq(Plugin.class), any())).thenReturn(Mono.just(plugin)); ConfigMap configMap = buildConfigMap(); - when(extensionClient.fetch(eq(ConfigMap.class), any())).thenReturn(Optional.of(configMap)); + when(extensionClient.fetch(eq(ConfigMap.class), any())).thenReturn(Mono.just(configMap)); } @Test @@ -72,6 +75,7 @@ class DefaultSettingFetcherTest { assertThat(sns.isEmpty()).isFalse(); JSONAssert.assertEquals(getSns(), JsonUtils.objectToJson(sns.get()), true); + when(extensionClient.fetch(eq(ConfigMap.class), any())).thenReturn(Mono.empty()); Optional missing = settingFetcher.fetch("sns1", Sns.class); assertThat(missing.isEmpty()).isTrue(); }