Skip to content

Commit 8423b2c

Browse files
committed
Add support for RSocket interface client
See gh-24456
1 parent ae861a2 commit 8423b2c

22 files changed

+1956
-93
lines changed

spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/Payload.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -51,7 +51,11 @@
5151
* <p>This attribute may or may not be supported depending on whether the message being
5252
* handled contains a non-primitive Object as its payload or is in serialized form and
5353
* requires message conversion.
54-
* <p>When processing STOMP over WebSocket messages this attribute is not supported.
54+
* <p>This attribute is not supported for:
55+
* <ul>
56+
* <li>STOMP over WebSocket messages</li>
57+
* <li>RSocket interface client</li>
58+
* </ul>
5559
* @since 4.2
5660
*/
5761
@AliasFor("value")

spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ public MimeType metadataMimeType() {
102102
return this.metadataMimeType;
103103
}
104104

105+
@Override
106+
public RSocketStrategies strategies() {
107+
return this.strategies;
108+
}
109+
110+
105111
@Override
106112
public RequestSpec route(String route, Object... vars) {
107113
return new DefaultRequestSpec(route, vars);

spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ public interface RSocketRequester extends Disposable {
8383
*/
8484
MimeType metadataMimeType();
8585

86+
/**
87+
* Return the configured {@link RSocketStrategies}.
88+
*/
89+
RSocketStrategies strategies();
90+
8691
/**
8792
* Begin to specify a new request with the given route to a remote handler.
8893
* <p>The route can be a template with placeholders, e.g.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.rsocket.service;
18+
19+
import java.util.Collection;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.messaging.handler.annotation.DestinationVariable;
24+
25+
/**
26+
* {@link RSocketServiceArgumentResolver} for a
27+
* {@link DestinationVariable @DestinationVariable} annotated argument.
28+
*
29+
* <p>The argument is treated as a single route variable, or in case of a
30+
* Collection or an array, as multiple route variables.
31+
*
32+
* @author Rossen Stoyanchev
33+
* @since 6.0
34+
*/
35+
public class DestinationVariableArgumentResolver implements RSocketServiceArgumentResolver {
36+
37+
@Override
38+
public boolean resolve(
39+
@Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
40+
41+
DestinationVariable annot = parameter.getParameterAnnotation(DestinationVariable.class);
42+
if (annot == null) {
43+
return false;
44+
}
45+
46+
if (argument != null) {
47+
if (argument instanceof Collection) {
48+
((Collection<?>) argument).forEach(requestValues::addRouteVariable);
49+
return true;
50+
}
51+
else if (argument.getClass().isArray()) {
52+
for (Object variable : (Object[]) argument) {
53+
requestValues.addRouteVariable(variable);
54+
}
55+
return true;
56+
}
57+
else {
58+
requestValues.addRouteVariable(argument);
59+
}
60+
}
61+
62+
return true;
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.rsocket.service;
18+
19+
import org.springframework.core.MethodParameter;
20+
import org.springframework.lang.Nullable;
21+
import org.springframework.util.Assert;
22+
import org.springframework.util.MimeType;
23+
24+
/**
25+
* {@link RSocketServiceArgumentResolver} for metadata entries.
26+
*
27+
* <p>Supports a sequence of an {@link Object} parameter for the metadata value,
28+
* followed by a {@link MimeType} parameter for the metadata mime type.
29+
*
30+
* <p>This should be ordered last to give other, more specific resolvers a
31+
* chance to resolve the argument.
32+
*
33+
* @author Rossen Stoyanchev
34+
* @since 6.0
35+
*/
36+
public class MetadataArgumentResolver implements RSocketServiceArgumentResolver {
37+
38+
@Override
39+
public boolean resolve(
40+
@Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
41+
42+
int index = parameter.getParameterIndex();
43+
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();
44+
45+
if (parameter.getParameterType().equals(MimeType.class)) {
46+
Assert.notNull(argument, "MimeType parameter is required");
47+
Assert.state(index > 0, "MimeType parameter should have preceding metadata object parameter");
48+
requestValues.addMimeType((MimeType) argument);
49+
return true;
50+
}
51+
52+
if (paramTypes.length > (index + 1) && MimeType.class.equals(paramTypes[index + 1])) {
53+
Assert.notNull(argument, "MimeType parameter is required");
54+
requestValues.addMetadata(argument);
55+
return true;
56+
}
57+
58+
return false;
59+
}
60+
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.rsocket.service;
18+
19+
import org.springframework.core.MethodParameter;
20+
import org.springframework.core.ParameterizedTypeReference;
21+
import org.springframework.core.ReactiveAdapter;
22+
import org.springframework.core.ReactiveAdapterRegistry;
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.messaging.handler.annotation.Payload;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* {@link RSocketServiceArgumentResolver} for {@link Payload @Payload}
29+
* annotated arguments.
30+
*
31+
* @author Rossen Stoyanchev
32+
* @since 6.0
33+
*/
34+
public class PayloadArgumentResolver implements RSocketServiceArgumentResolver {
35+
36+
private final ReactiveAdapterRegistry reactiveAdapterRegistry;
37+
38+
private final boolean useDefaultResolution;
39+
40+
41+
public PayloadArgumentResolver(ReactiveAdapterRegistry reactiveAdapterRegistry, boolean useDefaultResolution) {
42+
this.useDefaultResolution = useDefaultResolution;
43+
Assert.notNull(reactiveAdapterRegistry, "ReactiveAdapterRegistry is required");
44+
this.reactiveAdapterRegistry = reactiveAdapterRegistry;
45+
}
46+
47+
48+
@Override
49+
public boolean resolve(
50+
@Nullable Object argument, MethodParameter parameter, RSocketRequestValues.Builder requestValues) {
51+
52+
Payload annot = parameter.getParameterAnnotation(Payload.class);
53+
if (annot == null && !this.useDefaultResolution) {
54+
return false;
55+
}
56+
57+
if (argument != null) {
58+
ReactiveAdapter reactiveAdapter = this.reactiveAdapterRegistry.getAdapter(parameter.getParameterType());
59+
if (reactiveAdapter == null) {
60+
requestValues.setPayloadValue(argument);
61+
}
62+
else {
63+
MethodParameter nestedParameter = parameter.nested();
64+
65+
String message = "Async type for @Payload should produce value(s)";
66+
Assert.isTrue(nestedParameter.getNestedParameterType() != Void.class, message);
67+
Assert.isTrue(!reactiveAdapter.isNoValue(), message);
68+
69+
requestValues.setPayload(
70+
reactiveAdapter.toPublisher(argument),
71+
ParameterizedTypeReference.forType(nestedParameter.getNestedGenericParameterType()));
72+
}
73+
}
74+
75+
return true;
76+
}
77+
78+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.messaging.rsocket.service;
18+
19+
import java.lang.annotation.Documented;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation to declare a method on an RSocket service interface as an RSocket
27+
* endpoint. The endpoint route is defined statically through the annotation
28+
* attributes, and through the input method argument types.
29+
*
30+
* <p>Supported at the type level to express common attributes, to be inherited
31+
* by all methods, such as a base route.
32+
*
33+
* <p>Supported method arguments:
34+
* <table border="1">
35+
* <tr>
36+
* <th>Method Argument</th>
37+
* <th>Description</th>
38+
* <th>Resolver</th>
39+
* </tr>
40+
* <tr>
41+
* <td>{@link org.springframework.messaging.handler.annotation.DestinationVariable @DestinationVariable}</td>
42+
* <td>Add a route variable to expand into the route</td>
43+
* <td>{@link DestinationVariableArgumentResolver}</td>
44+
* </tr>
45+
* <tr>
46+
* <td>{@link org.springframework.messaging.handler.annotation.Payload @Payload}</td>
47+
* <td>Set the input payload(s) for the request</td>
48+
* <td>{@link PayloadArgumentResolver}</td>
49+
* </tr>
50+
* <tr>
51+
* <td>{@link Object} argument followed by {@link org.springframework.util.MimeType} argument</td>
52+
* <td>Add a metadata value</td>
53+
* <td>{@link MetadataArgumentResolver}</td>
54+
* </tr>
55+
* <tr>
56+
* <td>{@link org.springframework.util.MimeType} argument preceded by {@link Object} argument</td>
57+
* <td>Specify the mime type for the preceding metadata value</td>
58+
* <td>{@link MetadataArgumentResolver}</td>
59+
* </tr>
60+
* </table>
61+
*
62+
* @author Rossen Stoyanchev
63+
* @since 6.0
64+
*/
65+
@Target({ElementType.TYPE, ElementType.METHOD})
66+
@Retention(RetentionPolicy.RUNTIME)
67+
@Documented
68+
public @interface RSocketExchange {
69+
70+
/**
71+
* Destination-based mapping expressed by this annotation. This is either
72+
* {@link org.springframework.util.AntPathMatcher AntPathMatcher} or
73+
* {@link org.springframework.web.util.pattern.PathPattern PathPattern}
74+
* based pattern, depending on which is configured, matched to the route of
75+
* the stream request.
76+
*/
77+
String value() default "";
78+
79+
}

0 commit comments

Comments
 (0)