|
16 | 16 |
|
17 | 17 | package org.springframework.integration.rsocket.outbound;
|
18 | 18 |
|
19 |
| -import java.util.function.Consumer; |
20 |
| - |
21 | 19 | import org.reactivestreams.Publisher;
|
22 | 20 |
|
23 | 21 | import org.springframework.core.ParameterizedTypeReference;
|
|
26 | 24 | import org.springframework.integration.expression.ExpressionUtils;
|
27 | 25 | import org.springframework.integration.expression.ValueExpression;
|
28 | 26 | import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
|
| 27 | +import org.springframework.integration.rsocket.ClientRSocketConnector; |
| 28 | +import org.springframework.lang.Nullable; |
29 | 29 | import org.springframework.messaging.Message;
|
30 | 30 | import org.springframework.messaging.rsocket.RSocketRequester;
|
31 |
| -import org.springframework.messaging.rsocket.RSocketStrategies; |
| 31 | +import org.springframework.messaging.rsocket.RSocketRequesterMethodArgumentResolver; |
32 | 32 | import org.springframework.util.Assert;
|
33 | 33 | import org.springframework.util.ClassUtils;
|
34 |
| -import org.springframework.util.MimeType; |
35 |
| -import org.springframework.util.MimeTypeUtils; |
36 | 34 |
|
37 |
| -import io.rsocket.RSocketFactory; |
38 |
| -import io.rsocket.transport.ClientTransport; |
39 |
| -import reactor.core.Disposable; |
40 | 35 | import reactor.core.publisher.Mono;
|
41 | 36 |
|
42 | 37 | /**
|
43 |
| - * An Outbound Messaging Gateway for RSocket client requests. |
| 38 | + * An Outbound Messaging Gateway for RSocket requests. |
| 39 | + * The request logic is fully based on the {@link RSocketRequester}, which can be obtained from the |
| 40 | + * {@link ClientRSocketConnector} on the client side or from the |
| 41 | + * {@link RSocketRequesterMethodArgumentResolver#RSOCKET_REQUESTER_HEADER} request message header |
| 42 | + * on the server side. |
| 43 | + * <p> |
| 44 | + * An RSocket operation is determined by the configured {@link Command} or respective SpEL |
| 45 | + * expression to be evaluated at runtime against the request message. |
| 46 | + * By default the {@link Command#requestResponse} operation is used. |
| 47 | + * <p> |
| 48 | + * For a {@link Publisher}-based requests, it must be present in the request message {@code payload}. |
| 49 | + * The flattening via upstream {@link org.springframework.integration.channel.FluxMessageChannel} will work, too, |
| 50 | + * but this way we will lose a scope of particular request and every {@link Publisher} event |
| 51 | + * will be send in its own plain request. |
| 52 | + * <p> |
| 53 | + * If reply is a {@link reactor.core.publisher.Flux}, it is wrapped to the {@link Mono} to retain a request scope. |
| 54 | + * The downstream flow is responsible to obtain this {@link reactor.core.publisher.Flux} from a message payload |
| 55 | + * and subscribe to it by itself. The {@link Mono} reply from this component is subscribed from the downstream |
| 56 | + * {@link org.springframework.integration.channel.FluxMessageChannel} or it is adapted to the |
| 57 | + * {@link org.springframework.util.concurrent.ListenableFuture} otherwise. |
44 | 58 | *
|
45 | 59 | * @author Artem Bilan
|
46 | 60 | *
|
47 | 61 | * @since 5.2
|
48 | 62 | *
|
| 63 | + * @see Command |
49 | 64 | * @see RSocketRequester
|
50 | 65 | */
|
51 | 66 | public class RSocketOutboundGateway extends AbstractReplyProducingMessageHandler {
|
52 | 67 |
|
53 |
| - private final ClientTransport clientTransport; |
54 |
| - |
55 | 68 | private final Expression routeExpression;
|
56 | 69 |
|
57 |
| - private MimeType dataMimeType = MimeTypeUtils.TEXT_PLAIN; |
58 |
| - |
59 |
| - private Consumer<RSocketFactory.ClientRSocketFactory> factoryConfigurer = (clientRSocketFactory) -> { }; |
60 |
| - |
61 |
| - private Consumer<RSocketStrategies.Builder> strategiesConfigurer = (builder) -> { }; |
| 70 | + @Nullable |
| 71 | + private ClientRSocketConnector clientRSocketConnector; |
62 | 72 |
|
63 | 73 | private Expression commandExpression = new ValueExpression<>(Command.requestResponse);
|
64 | 74 |
|
65 | 75 | private Expression publisherElementTypeExpression = new ValueExpression<>(String.class);
|
66 | 76 |
|
67 | 77 | private Expression expectedResponseTypeExpression = new ValueExpression<>(String.class);
|
68 | 78 |
|
69 |
| - private Mono<RSocketRequester> rSocketRequesterMono; |
70 |
| - |
71 | 79 | private EvaluationContext evaluationContext;
|
72 | 80 |
|
73 |
| - public RSocketOutboundGateway(ClientTransport clientTransport, String route) { |
74 |
| - this(clientTransport, new ValueExpression<>(route)); |
| 81 | + @Nullable |
| 82 | + private Mono<RSocketRequester> rsocketRequesterMono; |
| 83 | + |
| 84 | + /** |
| 85 | + * Instantiate based on the provided RSocket endpoint {@code route}. |
| 86 | + * @param route the RSocket endpoint route to use. |
| 87 | + */ |
| 88 | + public RSocketOutboundGateway(String route) { |
| 89 | + this(new ValueExpression<>(route)); |
75 | 90 | }
|
76 | 91 |
|
77 |
| - public RSocketOutboundGateway(ClientTransport clientTransport, Expression routeExpression) { |
78 |
| - Assert.notNull(clientTransport, "'clientTransport' must not be null"); |
| 92 | + /** |
| 93 | + * Instantiate based on the provided SpEL expression to evaluate an RSocket endpoint {@code route} |
| 94 | + * at runtime against a request message. |
| 95 | + * @param routeExpression the SpEL expression to use. |
| 96 | + */ |
| 97 | + public RSocketOutboundGateway(Expression routeExpression) { |
79 | 98 | Assert.notNull(routeExpression, "'routeExpression' must not be null");
|
80 |
| - this.clientTransport = clientTransport; |
81 | 99 | this.routeExpression = routeExpression;
|
82 | 100 | setAsync(true);
|
83 | 101 | setPrimaryExpression(this.routeExpression);
|
84 | 102 | }
|
85 | 103 |
|
86 |
| - public void setDataMimeType(MimeType dataMimeType) { |
87 |
| - Assert.notNull(dataMimeType, "'dataMimeType' must not be null"); |
88 |
| - this.dataMimeType = dataMimeType; |
89 |
| - } |
90 |
| - |
91 |
| - public void setFactoryConfigurer(Consumer<RSocketFactory.ClientRSocketFactory> factoryConfigurer) { |
92 |
| - Assert.notNull(factoryConfigurer, "'factoryConfigurer' must not be null"); |
93 |
| - this.factoryConfigurer = factoryConfigurer; |
94 |
| - } |
95 |
| - |
96 |
| - public void setStrategiesConfigurer(Consumer<RSocketStrategies.Builder> strategiesConfigurer) { |
97 |
| - Assert.notNull(strategiesConfigurer, "'strategiesConfigurer' must not be null"); |
98 |
| - this.strategiesConfigurer = strategiesConfigurer; |
| 104 | + /** |
| 105 | + * Configure a {@link ClientRSocketConnector} for client side requests based on the connection |
| 106 | + * provided by the {@link ClientRSocketConnector#getRSocketRequester()}. |
| 107 | + * In case of server side, an {@link RSocketRequester} must be provided in the |
| 108 | + * {@link RSocketRequesterMethodArgumentResolver#RSOCKET_REQUESTER_HEADER} header of request message. |
| 109 | + * @param clientRSocketConnector the {@link ClientRSocketConnector} to use. |
| 110 | + */ |
| 111 | + public void setClientRSocketConnector(ClientRSocketConnector clientRSocketConnector) { |
| 112 | + Assert.notNull(clientRSocketConnector, "'clientRSocketConnector' must not be null"); |
| 113 | + this.clientRSocketConnector = clientRSocketConnector; |
99 | 114 | }
|
100 | 115 |
|
| 116 | + /** |
| 117 | + * Configure a {@link Command} for RSocket request type. |
| 118 | + * @param command the {@link Command} to use. |
| 119 | + */ |
101 | 120 | public void setCommand(Command command) {
|
102 | 121 | setCommandExpression(new ValueExpression<>(command));
|
103 | 122 | }
|
104 | 123 |
|
| 124 | + /** |
| 125 | + * Configure a SpEL expression to evaluate a {@link Command} for RSocket request type at runtime |
| 126 | + * against a request message. |
| 127 | + * @param commandExpression the SpEL expression to use. |
| 128 | + */ |
105 | 129 | public void setCommandExpression(Expression commandExpression) {
|
106 | 130 | Assert.notNull(commandExpression, "'commandExpression' must not be null");
|
107 | 131 | this.commandExpression = commandExpression;
|
@@ -155,26 +179,35 @@ public void setExpectedResponseTypeExpression(Expression expectedResponseTypeExp
|
155 | 179 | @Override
|
156 | 180 | protected void doInit() {
|
157 | 181 | super.doInit();
|
158 |
| - this.rSocketRequesterMono = |
159 |
| - RSocketRequester.builder() |
160 |
| - .rsocketFactory(this.factoryConfigurer) |
161 |
| - .rsocketStrategies(this.strategiesConfigurer) |
162 |
| - .connect(this.clientTransport, this.dataMimeType); |
163 |
| - |
164 | 182 | this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(getBeanFactory());
|
| 183 | + if (this.clientRSocketConnector != null) { |
| 184 | + this.rsocketRequesterMono = this.clientRSocketConnector.getRSocketRequester().cache(); |
| 185 | + } |
165 | 186 | }
|
166 | 187 |
|
167 | 188 | @Override
|
168 | 189 | public void destroy() {
|
169 | 190 | super.destroy();
|
170 |
| - this.rSocketRequesterMono.map(RSocketRequester::rsocket) |
171 |
| - .doOnNext(Disposable::dispose) |
172 |
| - .subscribe(); |
| 191 | + |
173 | 192 | }
|
174 | 193 |
|
175 | 194 | @Override
|
176 | 195 | protected Object handleRequestMessage(Message<?> requestMessage) {
|
177 |
| - return this.rSocketRequesterMono.cache() |
| 196 | + RSocketRequester rsocketRequester = requestMessage.getHeaders() |
| 197 | + .get(RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER, RSocketRequester.class); |
| 198 | + Mono<RSocketRequester> requesterMono; |
| 199 | + if (rsocketRequester != null) { |
| 200 | + requesterMono = Mono.just(rsocketRequester); |
| 201 | + } |
| 202 | + else { |
| 203 | + requesterMono = this.rsocketRequesterMono; |
| 204 | + } |
| 205 | + |
| 206 | + Assert.notNull(requesterMono, () -> |
| 207 | + "The 'RSocketRequester' must be configured via 'ClientRSocketConnector' or provided in the '" + |
| 208 | + RSocketRequesterMethodArgumentResolver.RSOCKET_REQUESTER_HEADER + "' request message headers."); |
| 209 | + |
| 210 | + return requesterMono |
178 | 211 | .map((rSocketRequester) -> createRequestSpec(rSocketRequester, requestMessage))
|
179 | 212 | .map((requestSpec) -> createResponseSpec(requestSpec, requestMessage))
|
180 | 213 | .flatMap((responseSpec) -> performRequest(responseSpec, requestMessage));
|
|
0 commit comments