mirror of https://github.com/halo-dev/halo
Refactor ExtensionGetter for enabling or disabling extensions (#6134)
#### What type of PR is this? /kind improvement /kind api-change /area core #### What this PR does / why we need it: This PR refactors ExtensionGetter implementation to add a support of enabling extension point(s). Here is an example of data field of `system` config map: ```json { "data": { "extensionPointEnabled": "{ \"search-engine\": [\"search-engine-algolia\"]}" }, ``` > 1. The `search-engine` is a name of extension point definition. > 2. The `search-engine-algolia` is a name of extension definition. #### Does this PR introduce a user-facing change? ```release-note None ```pull/6122/head^2
parent
705bd235c3
commit
e4cce918f7
|
@ -1,6 +1,7 @@
|
||||||
package run.halo.app.infra;
|
package run.halo.app.infra;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.convert.ApplicationConversionService;
|
import org.springframework.boot.convert.ApplicationConversionService;
|
||||||
|
@ -115,12 +116,10 @@ public class SystemSetting {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ExtensionPointEnabled key is full qualified name of extension point and value is a list of
|
* ExtensionPointEnabled key is metadata name of extension point and value is a list of
|
||||||
* full qualified name of implementation.
|
* extension definition names.
|
||||||
*/
|
*/
|
||||||
public static class ExtensionPointEnabled extends LinkedHashMap<String, Set<String>> {
|
public static class ExtensionPointEnabled extends LinkedHashMap<String, LinkedHashSet<String>> {
|
||||||
|
|
||||||
public static final ExtensionPointEnabled EMPTY = new ExtensionPointEnabled();
|
|
||||||
|
|
||||||
public static final String GROUP = "extensionPointEnabled";
|
public static final String GROUP = "extensionPointEnabled";
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class DefaultNotificationSender
|
||||||
|
|
||||||
Mono<ReactiveNotifier> selectNotifier(String notifierExtensionName) {
|
Mono<ReactiveNotifier> selectNotifier(String notifierExtensionName) {
|
||||||
return client.fetch(ExtensionDefinition.class, notifierExtensionName)
|
return client.fetch(ExtensionDefinition.class, notifierExtensionName)
|
||||||
.flatMap(extDefinition -> extensionGetter.getEnabledExtensionByDefinition(
|
.flatMap(extDefinition -> extensionGetter.getEnabledExtensions(
|
||||||
ReactiveNotifier.class)
|
ReactiveNotifier.class)
|
||||||
.filter(notifier -> notifier.getClass().getName()
|
.filter(notifier -> notifier.getClass().getName()
|
||||||
.equals(extDefinition.getSpec().getClassName())
|
.equals(extDefinition.getSpec().getClassName())
|
||||||
|
|
|
@ -2,17 +2,13 @@ package run.halo.app.plugin.extensionpoint;
|
||||||
|
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Objects;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.pf4j.ExtensionPoint;
|
import org.pf4j.ExtensionPoint;
|
||||||
import org.pf4j.PluginManager;
|
import org.pf4j.PluginManager;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -32,88 +28,65 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final BeanFactory beanFactory;
|
||||||
|
|
||||||
private final ReactiveExtensionClient client;
|
private final ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPoint) {
|
||||||
|
return Flux.fromIterable(pluginManager.getExtensions(extensionPoint))
|
||||||
|
.concatWith(
|
||||||
|
Flux.fromStream(() -> beanFactory.getBeanProvider(extensionPoint).orderedStream())
|
||||||
|
)
|
||||||
|
.sort(new AnnotationAwareOrderComparator());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint) {
|
public <T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint) {
|
||||||
return systemConfigFetcher.fetch(ExtensionPointEnabled.GROUP, ExtensionPointEnabled.class)
|
return getEnabledExtensions(extensionPoint).next();
|
||||||
.switchIfEmpty(Mono.just(ExtensionPointEnabled.EMPTY))
|
|
||||||
.mapNotNull(enabled -> {
|
|
||||||
var implClassNames = enabled.getOrDefault(extensionPoint.getName(), Set.of());
|
|
||||||
List<T> allExtensions = getAllExtensions(extensionPoint);
|
|
||||||
if (allExtensions.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return allExtensions
|
|
||||||
.stream()
|
|
||||||
.filter(impl -> implClassNames.contains(impl.getClass().getName()))
|
|
||||||
.findFirst()
|
|
||||||
// Fallback to local implementation of the extension point.
|
|
||||||
// This will happen when no proper configuration is found.
|
|
||||||
.orElseGet(() -> allExtensions.get(0));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends ExtensionPoint> Flux<T> getEnabledExtensions(Class<T> extensionPoint) {
|
public <T extends ExtensionPoint> Flux<T> getEnabledExtensions(
|
||||||
return systemConfigFetcher.fetch(ExtensionPointEnabled.GROUP, ExtensionPointEnabled.class)
|
|
||||||
.switchIfEmpty(Mono.just(ExtensionPointEnabled.EMPTY))
|
|
||||||
.flatMapMany(enabled -> {
|
|
||||||
var implClassNames = enabled.getOrDefault(extensionPoint.getName(), Set.of());
|
|
||||||
var extensions = pluginManager.getExtensions(extensionPoint)
|
|
||||||
.stream()
|
|
||||||
.filter(impl -> implClassNames.contains(impl.getClass().getName()))
|
|
||||||
.toList();
|
|
||||||
if (extensions.isEmpty()) {
|
|
||||||
extensions = applicationContext.getBeanProvider(extensionPoint)
|
|
||||||
.orderedStream()
|
|
||||||
// we only fetch one implementation here
|
|
||||||
.limit(1)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
return Flux.fromIterable(extensions);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ExtensionPoint> Flux<T> getEnabledExtensionByDefinition(
|
|
||||||
Class<T> extensionPoint) {
|
Class<T> extensionPoint) {
|
||||||
return fetchExtensionPointDefinition(extensionPoint)
|
return fetchExtensionPointDefinition(extensionPoint)
|
||||||
.flatMapMany(extensionPointDefinition -> {
|
.flatMapMany(epd -> {
|
||||||
ExtensionPointDefinition.ExtensionPointType type =
|
var epdName = epd.getMetadata().getName();
|
||||||
extensionPointDefinition.getSpec().getType();
|
var type = epd.getSpec().getType();
|
||||||
if (type == ExtensionPointDefinition.ExtensionPointType.SINGLETON) {
|
if (type == ExtensionPointDefinition.ExtensionPointType.SINGLETON) {
|
||||||
return getEnabledExtension(extensionPoint).flux();
|
return getEnabledExtensions(epdName, extensionPoint).take(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO If the type is sortable, may need to process the returned order.
|
// TODO If the type is sortable, may need to process the returned order.
|
||||||
return Flux.fromIterable(getAllExtensions(extensionPoint));
|
return getEnabledExtensions(epdName, extensionPoint);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private <T extends ExtensionPoint> Flux<T> getEnabledExtensions(String epdName,
|
||||||
public <T extends ExtensionPoint> Flux<T> getExtensions(Class<T> extensionPointClass) {
|
Class<T> extensionPoint) {
|
||||||
var extensions = new ArrayList<>(pluginManager.getExtensions(extensionPointClass));
|
return systemConfigFetcher.fetch(ExtensionPointEnabled.GROUP, ExtensionPointEnabled.class)
|
||||||
applicationContext.getBeanProvider(extensionPointClass)
|
.switchIfEmpty(Mono.fromSupplier(ExtensionPointEnabled::new))
|
||||||
.orderedStream()
|
.flatMapMany(enabled -> {
|
||||||
.forEach(extensions::add);
|
var extensionDefNames = enabled.getOrDefault(epdName, null);
|
||||||
return Flux.fromIterable(extensions);
|
if (extensionDefNames == null) {
|
||||||
|
// get all extensions if not specified
|
||||||
|
return Flux.defer(() -> getExtensions(extensionPoint));
|
||||||
|
}
|
||||||
|
var extensions = getExtensions(extensionPoint).cache();
|
||||||
|
return Flux.fromIterable(extensionDefNames)
|
||||||
|
.concatMap(extensionDefName ->
|
||||||
|
client.fetch(ExtensionDefinition.class, extensionDefName)
|
||||||
|
)
|
||||||
|
.concatMap(extensionDef -> {
|
||||||
|
var className = extensionDef.getSpec().getClassName();
|
||||||
|
return extensions.filter(
|
||||||
|
extension -> Objects.equals(extension.getClass().getName(),
|
||||||
|
className)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
private Mono<ExtensionPointDefinition> fetchExtensionPointDefinition(
|
||||||
<T extends ExtensionPoint> List<T> getAllExtensions(Class<T> extensionPoint) {
|
|
||||||
Stream<T> pluginExtsStream = pluginManager.getExtensions(extensionPoint)
|
|
||||||
.stream();
|
|
||||||
Stream<T> systemExtsStream = applicationContext.getBeanProvider(extensionPoint)
|
|
||||||
.orderedStream();
|
|
||||||
return Stream.concat(systemExtsStream, pluginExtsStream)
|
|
||||||
.sorted(new AnnotationAwareOrderComparator())
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Mono<ExtensionPointDefinition> fetchExtensionPointDefinition(
|
|
||||||
Class<? extends ExtensionPoint> extensionPoint) {
|
Class<? extends ExtensionPoint> extensionPoint) {
|
||||||
var listOptions = new ListOptions();
|
var listOptions = new ListOptions();
|
||||||
listOptions.setFieldSelector(FieldSelector.of(
|
listOptions.setFieldSelector(FieldSelector.of(
|
||||||
|
@ -125,4 +98,5 @@ public class DefaultExtensionGetter implements ExtensionGetter {
|
||||||
)
|
)
|
||||||
.flatMap(list -> Mono.justOrEmpty(ListResult.first(list)));
|
.flatMap(list -> Mono.justOrEmpty(ListResult.first(list)));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,6 @@ public interface ExtensionGetter {
|
||||||
*/
|
*/
|
||||||
<T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint);
|
<T extends ExtensionPoint> Mono<T> getEnabledExtension(Class<T> extensionPoint);
|
||||||
|
|
||||||
/**
|
|
||||||
* Get enabled extension list from system configuration.
|
|
||||||
*
|
|
||||||
* @param extensionPoint is extension point class.
|
|
||||||
* @return implementations of the corresponding extension point. If no configuration is found,
|
|
||||||
* we will use the default implementation from application context instead.
|
|
||||||
*/
|
|
||||||
<T extends ExtensionPoint> Flux<T> getEnabledExtensions(Class<T> extensionPoint);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the extension(s) according to the {@link ExtensionPointDefinition} queried
|
* Get the extension(s) according to the {@link ExtensionPointDefinition} queried
|
||||||
* by incoming extension point class.
|
* by incoming extension point class.
|
||||||
|
@ -33,7 +24,7 @@ public interface ExtensionGetter {
|
||||||
* @throws IllegalArgumentException if the incoming extension point class does not have
|
* @throws IllegalArgumentException if the incoming extension point class does not have
|
||||||
* the {@link ExtensionPointDefinition}.
|
* the {@link ExtensionPointDefinition}.
|
||||||
*/
|
*/
|
||||||
<T extends ExtensionPoint> Flux<T> getEnabledExtensionByDefinition(Class<T> extensionPoint);
|
<T extends ExtensionPoint> Flux<T> getEnabledExtensions(Class<T> extensionPoint);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all extensions according to extension point class.
|
* Get all extensions according to extension point class.
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class UsernamePasswordDelegatingAuthenticationManager
|
||||||
@Override
|
@Override
|
||||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||||
return extensionGetter
|
return extensionGetter
|
||||||
.getEnabledExtensionByDefinition(UsernamePasswordAuthenticationManager.class)
|
.getEnabledExtensions(UsernamePasswordAuthenticationManager.class)
|
||||||
.next()
|
.next()
|
||||||
.flatMap(authenticationManager -> authenticationManager.authenticate(authentication)
|
.flatMap(authenticationManager -> authenticationManager.authenticate(authentication)
|
||||||
.doOnError(t -> log.error(
|
.doOnError(t -> log.error(
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class CommentEnabledVariableProcessor extends AbstractTemplateBoundariesP
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class);
|
ExtensionGetter extensionGetter = appCtx.getBean(ExtensionGetter.class);
|
||||||
return extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class)
|
return extensionGetter.getEnabledExtensions(CommentWidget.class)
|
||||||
.next()
|
.next()
|
||||||
.blockOptional();
|
.blockOptional();
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,7 @@ public class PostPublicQueryServiceImpl implements PostPublicQueryService {
|
||||||
ContentWrapper wrapper) {
|
ContentWrapper wrapper) {
|
||||||
Assert.notNull(post, "Post name must not be null");
|
Assert.notNull(post, "Post name must not be null");
|
||||||
Assert.notNull(wrapper, "Post content must not be null");
|
Assert.notNull(wrapper, "Post content must not be null");
|
||||||
return extensionGetter.getEnabledExtensionByDefinition(ReactivePostContentHandler.class)
|
return extensionGetter.getEnabledExtensions(ReactivePostContentHandler.class)
|
||||||
.reduce(Mono.fromSupplier(() -> ReactivePostContentHandler.PostContentContext.builder()
|
.reduce(Mono.fromSupplier(() -> ReactivePostContentHandler.PostContentContext.builder()
|
||||||
.post(post)
|
.post(post)
|
||||||
.content(wrapper.getContent())
|
.content(wrapper.getContent())
|
||||||
|
|
|
@ -58,7 +58,7 @@ public class SinglePageConversionServiceImpl implements SinglePageConversionServ
|
||||||
ContentWrapper wrapper) {
|
ContentWrapper wrapper) {
|
||||||
Assert.notNull(singlePage, "SinglePage must not be null");
|
Assert.notNull(singlePage, "SinglePage must not be null");
|
||||||
Assert.notNull(wrapper, "SinglePage content must not be null");
|
Assert.notNull(wrapper, "SinglePage content must not be null");
|
||||||
return extensionGetter.getEnabledExtensionByDefinition(
|
return extensionGetter.getEnabledExtensions(
|
||||||
ReactiveSinglePageContentHandler.class)
|
ReactiveSinglePageContentHandler.class)
|
||||||
.reduce(Mono.fromSupplier(() -> SinglePageContentContext.builder()
|
.reduce(Mono.fromSupplier(() -> SinglePageContentContext.builder()
|
||||||
.singlePage(singlePage)
|
.singlePage(singlePage)
|
||||||
|
|
|
@ -24,7 +24,7 @@ public class AdditionalWebFilterChainProxy implements WebFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||||
return extensionGetter.getEnabledExtensionByDefinition(AdditionalWebFilter.class)
|
return extensionGetter.getEnabledExtensions(AdditionalWebFilter.class)
|
||||||
.sort(AnnotationAwareOrderComparator.INSTANCE)
|
.sort(AnnotationAwareOrderComparator.INSTANCE)
|
||||||
.cast(WebFilter.class)
|
.cast(WebFilter.class)
|
||||||
.collectList()
|
.collectList()
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
package run.halo.app.plugin.extensionpoint;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static run.halo.app.infra.SystemSetting.ExtensionPointEnabled.GROUP;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.pf4j.ExtensionPoint;
|
||||||
|
import org.pf4j.PluginManager;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.test.StepVerifier;
|
||||||
|
import run.halo.app.extension.ListOptions;
|
||||||
|
import run.halo.app.extension.ListResult;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||||
|
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
|
||||||
|
import run.halo.app.plugin.extensionpoint.ExtensionPointDefinition.ExtensionPointType;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class DefaultExtensionGetterTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
ReactiveExtensionClient client;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
PluginManager pluginManager;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
SystemConfigurableEnvironmentFetcher configFetcher;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
BeanFactory beanFactory;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
DefaultExtensionGetter getter;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetExtensionBySingletonDefinitionWhenExtensionPointEnabledSet() {
|
||||||
|
// prepare extension point definition
|
||||||
|
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||||
|
FakeExtensionPoint.class,
|
||||||
|
ExtensionPointType.SINGLETON);
|
||||||
|
return new ListResult<>(List.of(epd));
|
||||||
|
}));
|
||||||
|
|
||||||
|
when(client.fetch(ExtensionDefinition.class, "fake-extension"))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||||
|
"fake-extension",
|
||||||
|
FakeExtensionPointImpl.class,
|
||||||
|
"fake-extension-point")));
|
||||||
|
|
||||||
|
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var extensionPointEnabled = new ExtensionPointEnabled();
|
||||||
|
extensionPointEnabled.put("fake-extension-point",
|
||||||
|
new LinkedHashSet<>(List.of("fake-extension")));
|
||||||
|
return extensionPointEnabled;
|
||||||
|
}));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectProvider<FakeExtensionPoint> objectProvider = mock(ObjectProvider.class);
|
||||||
|
when(objectProvider.orderedStream())
|
||||||
|
.thenReturn(Stream.of(new FakeExtensionPointDefaultImpl()));
|
||||||
|
when(beanFactory.getBeanProvider(FakeExtensionPoint.class)).thenReturn(objectProvider);
|
||||||
|
|
||||||
|
var extensionImpl = new FakeExtensionPointImpl();
|
||||||
|
when(pluginManager.getExtensions(FakeExtensionPoint.class))
|
||||||
|
.thenReturn(List.of(extensionImpl));
|
||||||
|
|
||||||
|
getter.getEnabledExtensions(FakeExtensionPoint.class)
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.expectNext(extensionImpl)
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetDefaultSingletonDefinitionWhileExtensionPointEnabledNotSet() {
|
||||||
|
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||||
|
FakeExtensionPoint.class,
|
||||||
|
ExtensionPointType.SINGLETON);
|
||||||
|
return new ListResult<>(List.of(epd));
|
||||||
|
}));
|
||||||
|
|
||||||
|
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||||
|
.thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectProvider<FakeExtensionPoint> objectProvider = mock(ObjectProvider.class);
|
||||||
|
var extensionDefaultImpl = new FakeExtensionPointDefaultImpl();
|
||||||
|
when(objectProvider.orderedStream())
|
||||||
|
.thenReturn(Stream.of(extensionDefaultImpl));
|
||||||
|
when(beanFactory.getBeanProvider(FakeExtensionPoint.class)).thenReturn(objectProvider);
|
||||||
|
|
||||||
|
when(pluginManager.getExtensions(FakeExtensionPoint.class))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
getter.getEnabledExtensions(FakeExtensionPoint.class)
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
.expectNext(extensionDefaultImpl)
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledSet() {
|
||||||
|
// prepare extension point definition
|
||||||
|
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||||
|
FakeExtensionPoint.class,
|
||||||
|
ExtensionPointType.MULTI_INSTANCE);
|
||||||
|
return new ListResult<>(List.of(epd));
|
||||||
|
}));
|
||||||
|
|
||||||
|
when(client.fetch(ExtensionDefinition.class, "fake-extension"))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||||
|
"fake-extension",
|
||||||
|
FakeExtensionPointImpl.class,
|
||||||
|
"fake-extension-point")));
|
||||||
|
|
||||||
|
when(client.fetch(ExtensionDefinition.class, "default-fake-extension"))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> createExtensionDefinition(
|
||||||
|
"default-fake-extension",
|
||||||
|
FakeExtensionPointDefaultImpl.class,
|
||||||
|
"fake-extension-point")));
|
||||||
|
|
||||||
|
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var extensionPointEnabled = new ExtensionPointEnabled();
|
||||||
|
extensionPointEnabled.put("fake-extension-point",
|
||||||
|
new LinkedHashSet<>(List.of("default-fake-extension", "fake-extension")));
|
||||||
|
return extensionPointEnabled;
|
||||||
|
}));
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectProvider<FakeExtensionPoint> objectProvider = mock(ObjectProvider.class);
|
||||||
|
var extensionDefaultImpl = new FakeExtensionPointDefaultImpl();
|
||||||
|
when(objectProvider.orderedStream())
|
||||||
|
.thenReturn(Stream.of(extensionDefaultImpl));
|
||||||
|
when(beanFactory.getBeanProvider(FakeExtensionPoint.class)).thenReturn(objectProvider);
|
||||||
|
|
||||||
|
var extensionImpl = new FakeExtensionPointImpl();
|
||||||
|
var anotherExtensionImpl = new FakeExtensionPoint() {
|
||||||
|
};
|
||||||
|
when(pluginManager.getExtensions(FakeExtensionPoint.class))
|
||||||
|
.thenReturn(List.of(extensionImpl, anotherExtensionImpl));
|
||||||
|
|
||||||
|
getter.getEnabledExtensions(FakeExtensionPoint.class)
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
// should keep the order of enabled extensions
|
||||||
|
.expectNext(extensionDefaultImpl)
|
||||||
|
.expectNext(extensionImpl)
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetMultiInstanceExtensionWhileExtensionPointEnabledNotSet() {
|
||||||
|
// prepare extension point definition
|
||||||
|
when(client.listBy(same(ExtensionPointDefinition.class), any(ListOptions.class), any()))
|
||||||
|
.thenReturn(Mono.fromSupplier(() -> {
|
||||||
|
var epd = createExtensionPointDefinition("fake-extension-point",
|
||||||
|
FakeExtensionPoint.class,
|
||||||
|
ExtensionPointType.MULTI_INSTANCE);
|
||||||
|
return new ListResult<>(List.of(epd));
|
||||||
|
}));
|
||||||
|
|
||||||
|
when(configFetcher.fetch(GROUP, ExtensionPointEnabled.class))
|
||||||
|
.thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectProvider<FakeExtensionPoint> objectProvider = mock(ObjectProvider.class);
|
||||||
|
var extensionDefaultImpl = new FakeExtensionPointDefaultImpl();
|
||||||
|
when(objectProvider.orderedStream())
|
||||||
|
.thenReturn(Stream.of(extensionDefaultImpl));
|
||||||
|
when(beanFactory.getBeanProvider(FakeExtensionPoint.class)).thenReturn(objectProvider);
|
||||||
|
|
||||||
|
var extensionImpl = new FakeExtensionPointImpl();
|
||||||
|
var anotherExtensionImpl = new FakeExtensionPoint() {
|
||||||
|
};
|
||||||
|
when(pluginManager.getExtensions(FakeExtensionPoint.class))
|
||||||
|
.thenReturn(List.of(extensionImpl, anotherExtensionImpl));
|
||||||
|
|
||||||
|
getter.getEnabledExtensions(FakeExtensionPoint.class)
|
||||||
|
.as(StepVerifier::create)
|
||||||
|
// should keep the order according to @Order annotation
|
||||||
|
// order is 1
|
||||||
|
.expectNext(extensionImpl)
|
||||||
|
// order is 2
|
||||||
|
.expectNext(extensionDefaultImpl)
|
||||||
|
// order is not set
|
||||||
|
.expectNext(anotherExtensionImpl)
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FakeExtensionPoint extends ExtensionPoint {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Order(1)
|
||||||
|
static class FakeExtensionPointImpl implements FakeExtensionPoint {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Order(2)
|
||||||
|
static class FakeExtensionPointDefaultImpl implements FakeExtensionPoint {
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionDefinition createExtensionDefinition(String name, Class<?> clazz, String epdName) {
|
||||||
|
var ed = new ExtensionDefinition();
|
||||||
|
var metadata = new Metadata();
|
||||||
|
metadata.setName(name);
|
||||||
|
ed.setMetadata(metadata);
|
||||||
|
var spec = new ExtensionDefinition.ExtensionSpec();
|
||||||
|
spec.setClassName(clazz.getName());
|
||||||
|
spec.setExtensionPointName(epdName);
|
||||||
|
ed.setSpec(spec);
|
||||||
|
return ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtensionPointDefinition createExtensionPointDefinition(String name,
|
||||||
|
Class<?> clazz,
|
||||||
|
ExtensionPointType type) {
|
||||||
|
var epd = new ExtensionPointDefinition();
|
||||||
|
var metadata = new Metadata();
|
||||||
|
metadata.setName(name);
|
||||||
|
epd.setMetadata(metadata);
|
||||||
|
var spec = new ExtensionPointDefinition.ExtensionPointSpec();
|
||||||
|
spec.setClassName(clazz.getName());
|
||||||
|
spec.setType(type);
|
||||||
|
epd.setSpec(spec);
|
||||||
|
return epd;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -75,7 +75,7 @@ class CommentElementTagProcessorTest {
|
||||||
.thenReturn(Mono.just(commentSetting));
|
.thenReturn(Mono.just(commentSetting));
|
||||||
when(commentSetting.getEnable()).thenReturn(true);
|
when(commentSetting.getEnable()).thenReturn(true);
|
||||||
|
|
||||||
when(extensionGetter.getEnabledExtensionByDefinition(eq(CommentWidget.class)))
|
when(extensionGetter.getEnabledExtensions(eq(CommentWidget.class)))
|
||||||
.thenReturn(Flux.empty());
|
.thenReturn(Flux.empty());
|
||||||
String result = templateEngine.process("commentWidget", context);
|
String result = templateEngine.process("commentWidget", context);
|
||||||
assertThat(result).isEqualTo("""
|
assertThat(result).isEqualTo("""
|
||||||
|
@ -88,7 +88,7 @@ class CommentElementTagProcessorTest {
|
||||||
</html>
|
</html>
|
||||||
""");
|
""");
|
||||||
|
|
||||||
when(extensionGetter.getEnabledExtensionByDefinition(eq(CommentWidget.class)))
|
when(extensionGetter.getEnabledExtensions(eq(CommentWidget.class)))
|
||||||
.thenReturn(Flux.just(new DefaultCommentWidget()));
|
.thenReturn(Flux.just(new DefaultCommentWidget()));
|
||||||
result = templateEngine.process("commentWidget", context);
|
result = templateEngine.process("commentWidget", context);
|
||||||
assertThat(result).isEqualTo("""
|
assertThat(result).isEqualTo("""
|
||||||
|
|
|
@ -54,7 +54,7 @@ class CommentEnabledVariableProcessorTest {
|
||||||
.thenReturn(Mono.just(commentSetting));
|
.thenReturn(Mono.just(commentSetting));
|
||||||
|
|
||||||
CommentWidget commentWidget = mock(CommentWidget.class);
|
CommentWidget commentWidget = mock(CommentWidget.class);
|
||||||
when(extensionGetter.getEnabledExtensionByDefinition(CommentWidget.class))
|
when(extensionGetter.getEnabledExtensions(CommentWidget.class))
|
||||||
.thenReturn(Flux.just(commentWidget));
|
.thenReturn(Flux.just(commentWidget));
|
||||||
WebEngineContext webContext = mock(WebEngineContext.class);
|
WebEngineContext webContext = mock(WebEngineContext.class);
|
||||||
var evaluationContext = mock(ThymeleafEvaluationContext.class);
|
var evaluationContext = mock(ThymeleafEvaluationContext.class);
|
||||||
|
|
|
@ -35,7 +35,7 @@ class PostPublicQueryServiceImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void extendPostContent() {
|
void extendPostContent() {
|
||||||
when(extensionGetter.getEnabledExtensionByDefinition(
|
when(extensionGetter.getEnabledExtensions(
|
||||||
eq(ReactivePostContentHandler.class))).thenReturn(
|
eq(ReactivePostContentHandler.class))).thenReturn(
|
||||||
Flux.just(new PostContentHandlerB(), new PostContentHandlerA(),
|
Flux.just(new PostContentHandlerB(), new PostContentHandlerA(),
|
||||||
new PostContentHandlerC()));
|
new PostContentHandlerC()));
|
||||||
|
|
|
@ -36,7 +36,7 @@ class SinglePageConversionServiceImplTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void extendPageContent() {
|
void extendPageContent() {
|
||||||
when(extensionGetter.getEnabledExtensionByDefinition(
|
when(extensionGetter.getEnabledExtensions(
|
||||||
eq(ReactiveSinglePageContentHandler.class)))
|
eq(ReactiveSinglePageContentHandler.class)))
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
Flux.just(new PageContentHandlerB(),
|
Flux.just(new PageContentHandlerB(),
|
||||||
|
|
Loading…
Reference in New Issue