Skip to content

Commit bcf4f39

Browse files
committed
Reactive InvocableHandlerMethod in spring-messaging
See gh-21987
1 parent 9e873af commit bcf4f39

File tree

9 files changed

+890
-0
lines changed

9 files changed

+890
-0
lines changed

spring-messaging/spring-messaging.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies {
2424
exclude group: "org.springframework", module: "spring-context"
2525
}
2626
testCompile("org.apache.activemq:activemq-stomp:5.8.0")
27+
testCompile("io.projectreactor:reactor-test")
2728
testCompile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
2829
testCompile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
2930
testCompile("org.xmlunit:xmlunit-matchers:2.6.2")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2002-2018 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+
* http://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+
package org.springframework.messaging.handler.invocation.reactive;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import org.springframework.core.MethodParameter;
21+
import org.springframework.messaging.Message;
22+
23+
/**
24+
* Strategy interface for resolving method parameters into argument values
25+
* in the context of a given {@link Message}.
26+
*
27+
* @author Rossen Stoyanchev
28+
* @since 5.2
29+
*/
30+
public interface HandlerMethodArgumentResolver {
31+
32+
/**
33+
* Whether the given {@linkplain MethodParameter method parameter} is
34+
* supported by this resolver.
35+
* @param parameter the method parameter to check
36+
* @return {@code true} if this resolver supports the supplied parameter;
37+
* {@code false} otherwise
38+
*/
39+
boolean supportsParameter(MethodParameter parameter);
40+
41+
/**
42+
* Resolves a method parameter into an argument value from a given message.
43+
* @param parameter the method parameter to resolve.
44+
* This parameter must have previously been passed to
45+
* {@link #supportsParameter(org.springframework.core.MethodParameter)}
46+
* which must have returned {@code true}.
47+
* @param message the currently processed message
48+
* @return {@code Mono} for the argument value, possibly empty
49+
*/
50+
Mono<Object> resolveArgument(MethodParameter parameter, Message<?> message);
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2002-2018 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+
* http://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.handler.invocation.reactive;
18+
19+
import java.util.Collections;
20+
import java.util.LinkedList;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.concurrent.ConcurrentHashMap;
24+
25+
import org.apache.commons.logging.Log;
26+
import org.apache.commons.logging.LogFactory;
27+
import reactor.core.publisher.Mono;
28+
29+
import org.springframework.core.MethodParameter;
30+
import org.springframework.lang.Nullable;
31+
import org.springframework.messaging.Message;
32+
33+
/**
34+
* Resolves method parameters by delegating to a list of registered
35+
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
36+
* Previously resolved method parameters are cached for faster lookups.
37+
*
38+
* @author Rossen Stoyanchev
39+
* @since 5.2
40+
*/
41+
class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
42+
43+
protected final Log logger = LogFactory.getLog(getClass());
44+
45+
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
46+
47+
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
48+
new ConcurrentHashMap<>(256);
49+
50+
51+
/**
52+
* Add the given {@link HandlerMethodArgumentResolver}.
53+
*/
54+
public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) {
55+
this.argumentResolvers.add(resolver);
56+
return this;
57+
}
58+
59+
/**
60+
* Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
61+
*/
62+
public HandlerMethodArgumentResolverComposite addResolvers(@Nullable HandlerMethodArgumentResolver... resolvers) {
63+
if (resolvers != null) {
64+
Collections.addAll(this.argumentResolvers, resolvers);
65+
}
66+
return this;
67+
}
68+
69+
/**
70+
* Add the given {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
71+
*/
72+
public HandlerMethodArgumentResolverComposite addResolvers(
73+
@Nullable List<? extends HandlerMethodArgumentResolver> resolvers) {
74+
75+
if (resolvers != null) {
76+
this.argumentResolvers.addAll(resolvers);
77+
}
78+
return this;
79+
}
80+
81+
/**
82+
* Return a read-only list with the contained resolvers, or an empty list.
83+
*/
84+
public List<HandlerMethodArgumentResolver> getResolvers() {
85+
return Collections.unmodifiableList(this.argumentResolvers);
86+
}
87+
88+
/**
89+
* Clear the list of configured resolvers.
90+
*/
91+
public void clear() {
92+
this.argumentResolvers.clear();
93+
}
94+
95+
96+
/**
97+
* Whether the given {@linkplain MethodParameter method parameter} is
98+
* supported by any registered {@link HandlerMethodArgumentResolver}.
99+
*/
100+
@Override
101+
public boolean supportsParameter(MethodParameter parameter) {
102+
return getArgumentResolver(parameter) != null;
103+
}
104+
105+
/**
106+
* Iterate over registered
107+
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} and
108+
* invoke the one that supports it.
109+
* @throws IllegalStateException if no suitable
110+
* {@link HandlerMethodArgumentResolver} is found.
111+
*/
112+
@Override
113+
public Mono<Object> resolveArgument(MethodParameter parameter, Message<?> message) {
114+
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
115+
if (resolver == null) {
116+
throw new IllegalArgumentException(
117+
"Unsupported parameter type [" + parameter.getParameterType().getName() + "]." +
118+
" supportsParameter should be called first.");
119+
}
120+
return resolver.resolveArgument(parameter, message);
121+
}
122+
123+
/**
124+
* Find a registered {@link HandlerMethodArgumentResolver} that supports
125+
* the given method parameter.
126+
*/
127+
@Nullable
128+
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
129+
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
130+
if (result == null) {
131+
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
132+
if (methodArgumentResolver.supportsParameter(parameter)) {
133+
result = methodArgumentResolver;
134+
this.argumentResolverCache.put(parameter, result);
135+
break;
136+
}
137+
}
138+
}
139+
return result;
140+
}
141+
142+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2002-2017 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+
* http://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.handler.invocation.reactive;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
import org.springframework.core.MethodParameter;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.messaging.Message;
24+
25+
/**
26+
* Handle the return value from the invocation of an annotated {@link Message}
27+
* handling method.
28+
*
29+
* @author Rossen Stoyanchev
30+
* @since 5.2
31+
*/
32+
public interface HandlerMethodReturnValueHandler {
33+
34+
/**
35+
* Whether the given {@linkplain MethodParameter method return type} is
36+
* supported by this handler.
37+
* @param returnType the method return type to check
38+
* @return {@code true} if this handler supports the supplied return type;
39+
* {@code false} otherwise
40+
*/
41+
boolean supportsReturnType(MethodParameter returnType);
42+
43+
/**
44+
* Handle the given return value.
45+
* @param returnValue the value returned from the handler method
46+
* @param returnType the type of the return value. This type must have previously
47+
* been passed to {@link #supportsReturnType(MethodParameter)}
48+
* and it must have returned {@code true}.
49+
* @return {@code Mono<Void>} to indicate when handling is complete.
50+
*/
51+
Mono<Void> handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, Message<?> message);
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2002-2018 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+
* http://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.handler.invocation.reactive;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import org.apache.commons.logging.Log;
24+
import org.apache.commons.logging.LogFactory;
25+
import reactor.core.publisher.Mono;
26+
27+
import org.springframework.core.MethodParameter;
28+
import org.springframework.lang.Nullable;
29+
import org.springframework.messaging.Message;
30+
31+
/**
32+
* A HandlerMethodReturnValueHandler that wraps and delegates to others.
33+
*
34+
* @author Rossen Stoyanchev
35+
* @since 5.2
36+
*/
37+
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
38+
39+
protected final Log logger = LogFactory.getLog(getClass());
40+
41+
42+
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
43+
44+
45+
/**
46+
* Return a read-only list with the configured handlers.
47+
*/
48+
public List<HandlerMethodReturnValueHandler> getReturnValueHandlers() {
49+
return Collections.unmodifiableList(this.returnValueHandlers);
50+
}
51+
52+
/**
53+
* Clear the list of configured handlers.
54+
*/
55+
public void clear() {
56+
this.returnValueHandlers.clear();
57+
}
58+
59+
/**
60+
* Add the given {@link HandlerMethodReturnValueHandler}.
61+
*/
62+
public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValueHandler) {
63+
this.returnValueHandlers.add(returnValueHandler);
64+
return this;
65+
}
66+
67+
/**
68+
* Add the given {@link HandlerMethodReturnValueHandler HandlerMethodReturnValueHandlers}.
69+
*/
70+
public HandlerMethodReturnValueHandlerComposite addHandlers(
71+
@Nullable List<? extends HandlerMethodReturnValueHandler> handlers) {
72+
73+
if (handlers != null) {
74+
this.returnValueHandlers.addAll(handlers);
75+
}
76+
return this;
77+
}
78+
79+
@Override
80+
public boolean supportsReturnType(MethodParameter returnType) {
81+
return getReturnValueHandler(returnType) != null;
82+
}
83+
84+
@Override
85+
public Mono<Void> handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, Message<?> message) {
86+
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
87+
if (handler == null) {
88+
throw new IllegalStateException("No handler for return value type: " + returnType.getParameterType());
89+
}
90+
if (logger.isTraceEnabled()) {
91+
logger.trace("Processing return value with " + handler);
92+
}
93+
return handler.handleReturnValue(returnValue, returnType, message);
94+
}
95+
96+
@SuppressWarnings("ForLoopReplaceableByForEach")
97+
@Nullable
98+
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
99+
for (int i = 0; i < this.returnValueHandlers.size(); i++) {
100+
HandlerMethodReturnValueHandler handler = this.returnValueHandlers.get(i);
101+
if (handler.supportsReturnType(returnType)) {
102+
return handler;
103+
}
104+
}
105+
return null;
106+
}
107+
108+
}

0 commit comments

Comments
 (0)