mirror of https://github.com/halo-dev/halo
feat: add reactive setting fetcher for plugin (#3625)
#### 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 供插件获取配置 ```pull/3640/head
parent
31e5014dec
commit
d355e797bd
|
@ -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 {
|
||||
|
||||
<T> Mono<T> fetch(String group, Class<T> clazz);
|
||||
|
||||
@NonNull
|
||||
Mono<JsonNode> get(String group);
|
||||
|
||||
@NonNull
|
||||
Mono<Map<String, JsonNode>> getValues();
|
||||
}
|
|
@ -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 <T> Mono<T> fetch(String group, Class<T> clazz) {
|
||||
return getInternal(group)
|
||||
.mapNotNull(jsonNode -> convertValue(jsonNode, clazz));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<JsonNode> get(String group) {
|
||||
return getInternal(group)
|
||||
.switchIfEmpty(
|
||||
Mono.error(new IllegalArgumentException("Group [" + group + "] does not exist."))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<Map<String, JsonNode>> getValues() {
|
||||
return getValuesInternal()
|
||||
.map(Map::copyOf)
|
||||
.defaultIfEmpty(Map.of());
|
||||
}
|
||||
|
||||
private Mono<JsonNode> getInternal(String group) {
|
||||
return getValuesInternal()
|
||||
.mapNotNull(values -> values.get(group))
|
||||
.defaultIfEmpty(JsonNodeFactory.instance.missingNode());
|
||||
}
|
||||
|
||||
private Mono<Map<String, JsonNode>> getValuesInternal() {
|
||||
return configMap(pluginName)
|
||||
.mapNotNull(ConfigMap::getData)
|
||||
.map(data -> {
|
||||
Map<String, JsonNode> result = new LinkedHashMap<>();
|
||||
data.forEach((key, value) -> result.put(key, readTree(value)));
|
||||
return result;
|
||||
})
|
||||
.defaultIfEmpty(Map.of());
|
||||
}
|
||||
|
||||
private Mono<ConfigMap> 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> T convertValue(JsonNode jsonNode, Class<T> clazz) {
|
||||
return JsonUtils.DEFAULT_JSON_MAPPER.convertValue(jsonNode, clazz);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* <p>A value fetcher for plugin form configuration.</p>
|
||||
|
@ -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 <T> Optional<T> fetch(String group, Class<T> 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<String, JsonNode> getValues() {
|
||||
return Map.copyOf(getValuesInternal());
|
||||
}
|
||||
|
||||
private JsonNode getInternal(String group) {
|
||||
return Optional.ofNullable(getValuesInternal().get(group))
|
||||
.orElse(JsonNodeFactory.instance.missingNode());
|
||||
}
|
||||
|
||||
private Map<String, JsonNode> 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> 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> T convertValue(JsonNode jsonNode, Class<T> clazz) {
|
||||
return JsonUtils.DEFAULT_JSON_MAPPER.convertValue(jsonNode, clazz);
|
||||
return Objects.requireNonNull(delegateFetcher.getValues().block());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<Sns> missing = settingFetcher.fetch("sns1", Sns.class);
|
||||
assertThat(missing.isEmpty()).isTrue();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue