Skip to content

Commit

Permalink
Add support defining custom endpoint in plugin (#3703)
Browse files Browse the repository at this point in the history
#### What type of PR is this?

/kind feature
/area core
/area plugin

#### What this PR does / why we need it:

Including CustomEndpoint beans while building plugin router functions.

After that, we can define a CustomEndpoint in plugin like this:

```java
@component
public class ApplicationEndpoint implements CustomEndpoint {

    @OverRide
    public RouterFunction<ServerResponse> endpoint() {
        return SpringdocRouteBuilder.route()
            .GET("/applications", request -> ServerResponse.ok().build(), builder -> {
                builder.operationId("ListV1Alpha1Applications");
            })
            .build();
    }

    @OverRide
    public GroupVersion groupVersion() {
        return CustomEndpoint.super.groupVersion();
    }
}
```

#### Which issue(s) this PR fixes:

Fixes #3700

#### Does this PR introduce a user-facing change?

```release-note
None
```
  • Loading branch information
JohnNiang committed Apr 7, 2023
1 parent 9b8cec4 commit dea87b2
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 14 deletions.
Expand Up @@ -8,6 +8,7 @@
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import run.halo.app.extension.GroupVersion;

public class CustomEndpointsBuilder {
Expand Down Expand Up @@ -35,6 +36,10 @@ public RouterFunction<ServerResponse> build() {
.tag(gv + "/CustomEndpoint")
);
});
if (routerFunctionsMap.isEmpty()) {
// return empty route.
return request -> Mono.empty();
}
routerFunctionsMap.clear();
return routeBuilder.build();
}
Expand Down
@@ -1,7 +1,9 @@
package run.halo.app.plugin;

import static run.halo.app.plugin.ExtensionContextRegistry.getInstance;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.HandlerFunction;
Expand All @@ -11,6 +13,8 @@
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;

/**
Expand Down Expand Up @@ -44,15 +48,27 @@ public void accept(@NonNull RouterFunctions.Visitor visitor) {

@SuppressWarnings("unchecked")
private List<RouterFunction<ServerResponse>> routerFunctions() {
Stream<RouterFunction<ServerResponse>> routerFunctionStream =
ExtensionContextRegistry.getInstance().getPluginApplicationContexts()
.stream()
.flatMap(applicationContext -> applicationContext
.getBeanProvider(RouterFunction.class)
.orderedStream())
.map(router -> (RouterFunction<ServerResponse>) router);
return Stream.concat(routerFunctionStream,
reverseProxyRouterFunctionFactory.getRouterFunctions().stream())
var rawRouterFunctions = getInstance().getPluginApplicationContexts()
.stream()
.flatMap(applicationContext -> applicationContext
.getBeanProvider(RouterFunction.class)
.orderedStream())
.map(router -> (RouterFunction<ServerResponse>) router)
.toList();
var reverseProxies = reverseProxyRouterFunctionFactory.getRouterFunctions();

var endpointBuilder = new CustomEndpointsBuilder();
getInstance().getPluginApplicationContexts()
.forEach(context -> context.getBeanProvider(CustomEndpoint.class)
.orderedStream()
.forEach(endpointBuilder::add));
var customEndpoint = endpointBuilder.build();

List<RouterFunction<ServerResponse>> routerFunctions =
new ArrayList<>(rawRouterFunctions.size() + reverseProxies.size() + 1);
routerFunctions.addAll(rawRouterFunctions);
routerFunctions.addAll(reverseProxies);
routerFunctions.add(customEndpoint);
return routerFunctions;
}
}
@@ -1,13 +1,19 @@
package run.halo.app.plugin;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
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.springframework.beans.factory.ObjectProvider;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
Expand All @@ -18,6 +24,7 @@
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;

/**
Expand All @@ -28,18 +35,35 @@
*/
@ExtendWith(MockitoExtension.class)
class PluginCompositeRouterFunctionTest {
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();

@Mock
private ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry;
ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry;

private PluginCompositeRouterFunction compositeRouterFunction;
@Mock
ObjectProvider<RouterFunction> rawRouterFunctionsProvider;

@Mock
ObjectProvider<CustomEndpoint> customEndpointsProvider;

private HandlerFunction<ServerResponse> handlerFunction;
@InjectMocks
PluginCompositeRouterFunction compositeRouterFunction;

HandlerFunction<ServerResponse> handlerFunction;

@BeforeEach
@SuppressWarnings("unchecked")
void setUp() {
var fakeContext = mock(PluginApplicationContext.class);
ExtensionContextRegistry.getInstance().register("fake-plugin", fakeContext);

when(rawRouterFunctionsProvider.orderedStream()).thenReturn(Stream.empty());
when(customEndpointsProvider.orderedStream()).thenReturn(Stream.empty());

when(fakeContext.getBeanProvider(RouterFunction.class))
.thenReturn(rawRouterFunctionsProvider);
when(fakeContext.getBeanProvider(CustomEndpoint.class)).thenReturn(customEndpointsProvider);

compositeRouterFunction =
new PluginCompositeRouterFunction(reverseProxyRouterFunctionRegistry);

Expand All @@ -50,6 +74,11 @@ void setUp() {
.thenReturn(List.of(routerFunction));
}

@AfterEach
void cleanUp() {
ExtensionContextRegistry.getInstance().remove("fake-plugin");
}

@Test
void route() {
RouterFunctionMapping mapping = new RouterFunctionMapping(compositeRouterFunction);
Expand All @@ -61,6 +90,9 @@ void route() {
.expectNext(handlerFunction)
.expectComplete()
.verify();

verify(rawRouterFunctionsProvider).orderedStream();
verify(customEndpointsProvider).orderedStream();
}

private ServerWebExchange createExchange(String urlTemplate) {
Expand Down

0 comments on commit dea87b2

Please sign in to comment.