Skip to content

Commit ccbb4bd

Browse files
committed
PayloadMethodArgumentResolver supports Optional
Closes gh-28945
1 parent 5b79a57 commit ccbb4bd

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

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

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 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.
@@ -17,6 +17,7 @@
1717
package org.springframework.messaging.handler.annotation.support;
1818

1919
import java.lang.annotation.Annotation;
20+
import java.util.Optional;
2021

2122
import org.springframework.core.MethodParameter;
2223
import org.springframework.core.annotation.AnnotationUtils;
@@ -27,6 +28,7 @@
2728
import org.springframework.messaging.converter.SmartMessageConverter;
2829
import org.springframework.messaging.handler.annotation.Payload;
2930
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
31+
import org.springframework.messaging.support.MessageBuilder;
3032
import org.springframework.util.Assert;
3133
import org.springframework.util.ClassUtils;
3234
import org.springframework.util.ObjectUtils;
@@ -113,24 +115,30 @@ public Object resolveArgument(MethodParameter parameter, Message<?> message) thr
113115
throw new IllegalStateException("@Payload SpEL expressions not supported by this resolver");
114116
}
115117

118+
boolean isOptionalTargetClass = (parameter.getParameterType() == Optional.class);
116119
Object payload = message.getPayload();
117120
if (isEmptyPayload(payload)) {
118-
if (ann == null || ann.required()) {
121+
if ((ann == null || ann.required()) && !isOptionalTargetClass) {
119122
String paramName = getParameterName(parameter);
120123
BindingResult bindingResult = new BeanPropertyBindingResult(payload, paramName);
121124
bindingResult.addError(new ObjectError(paramName, "Payload value must not be empty"));
122125
throw new MethodArgumentNotValidException(message, parameter, bindingResult);
123126
}
124127
else {
125-
return null;
128+
return (isOptionalTargetClass ? Optional.empty() : null);
126129
}
127130
}
128131

132+
if (payload instanceof Optional<?> optional) {
133+
payload = optional.get();
134+
message = MessageBuilder.createMessage(payload, message.getHeaders());
135+
}
136+
129137
Class<?> targetClass = resolveTargetClass(parameter, message);
130138
Class<?> payloadClass = payload.getClass();
131139
if (ClassUtils.isAssignable(targetClass, payloadClass)) {
132140
validate(message, parameter, payload);
133-
return payload;
141+
return (isOptionalTargetClass ? Optional.of(payload) : payload);
134142
}
135143
else {
136144
if (this.converter instanceof SmartMessageConverter smartConverter) {
@@ -144,7 +152,7 @@ public Object resolveArgument(MethodParameter parameter, Message<?> message) thr
144152
payloadClass.getName() + "] to [" + targetClass.getName() + "] for " + message);
145153
}
146154
validate(message, parameter, payload);
147-
return payload;
155+
return (isOptionalTargetClass ? Optional.of(payload) : payload);
148156
}
149157
}
150158

@@ -161,11 +169,14 @@ protected boolean isEmptyPayload(@Nullable Object payload) {
161169
if (payload == null) {
162170
return true;
163171
}
164-
else if (payload instanceof byte[]) {
165-
return ((byte[]) payload).length == 0;
172+
else if (payload instanceof byte[] bytes) {
173+
return bytes.length == 0;
174+
}
175+
else if (payload instanceof String s) {
176+
return !StringUtils.hasText(s);
166177
}
167-
else if (payload instanceof String) {
168-
return !StringUtils.hasText((String) payload);
178+
else if (payload instanceof Optional<?> optional) {
179+
return optional.isEmpty();
169180
}
170181
else {
171182
return false;
@@ -184,7 +195,7 @@ else if (payload instanceof String) {
184195
* @since 5.2
185196
*/
186197
protected Class<?> resolveTargetClass(MethodParameter parameter, Message<?> message) {
187-
return parameter.getParameterType();
198+
return parameter.nestedIfOptional().getNestedParameterType();
188199
}
189200

190201
/**

spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadMethodArgumentResolverTests.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.lang.annotation.Target;
2323
import java.lang.reflect.Method;
2424
import java.util.Locale;
25+
import java.util.Optional;
2526

2627
import org.junit.jupiter.api.BeforeEach;
2728
import org.junit.jupiter.api.Test;
@@ -61,6 +62,8 @@ public class PayloadMethodArgumentResolverTests {
6162

6263
private MethodParameter paramWithSpelExpression;
6364

65+
private MethodParameter paramOptional;
66+
6467
private MethodParameter paramNotAnnotated;
6568

6669
private MethodParameter paramValidatedNotAnnotated;
@@ -74,16 +77,17 @@ public void setup() throws Exception {
7477

7578
Method payloadMethod = PayloadMethodArgumentResolverTests.class.getDeclaredMethod(
7679
"handleMessage", String.class, String.class, Locale.class,
77-
String.class, String.class, String.class, String.class);
80+
String.class, Optional.class, String.class, String.class, String.class);
7881

7982
this.paramAnnotated = new SynthesizingMethodParameter(payloadMethod, 0);
8083
this.paramAnnotatedNotRequired = new SynthesizingMethodParameter(payloadMethod, 1);
8184
this.paramAnnotatedRequired = new SynthesizingMethodParameter(payloadMethod, 2);
8285
this.paramWithSpelExpression = new SynthesizingMethodParameter(payloadMethod, 3);
83-
this.paramValidated = new SynthesizingMethodParameter(payloadMethod, 4);
86+
this.paramOptional = new SynthesizingMethodParameter(payloadMethod, 4);
87+
this.paramValidated = new SynthesizingMethodParameter(payloadMethod, 5);
8488
this.paramValidated.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
85-
this.paramValidatedNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 5);
86-
this.paramNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 6);
89+
this.paramValidatedNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 6);
90+
this.paramNotAnnotated = new SynthesizingMethodParameter(payloadMethod, 7);
8791
}
8892

8993
@Test
@@ -127,13 +131,33 @@ public void resolveNotRequired() throws Exception {
127131
Message<?> emptyByteArrayMessage = MessageBuilder.withPayload(new byte[0]).build();
128132
assertThat(this.resolver.resolveArgument(this.paramAnnotatedNotRequired, emptyByteArrayMessage)).isNull();
129133

130-
Message<?> emptyStringMessage = MessageBuilder.withPayload("").build();
134+
Message<?> emptyStringMessage = MessageBuilder.withPayload(" ").build();
131135
assertThat(this.resolver.resolveArgument(this.paramAnnotatedNotRequired, emptyStringMessage)).isNull();
136+
assertThat(((Optional<?>) this.resolver.resolveArgument(this.paramOptional, emptyStringMessage)).isEmpty()).isTrue();
137+
138+
Message<?> emptyOptionalMessage = MessageBuilder.withPayload(Optional.empty()).build();
139+
assertThat(this.resolver.resolveArgument(this.paramAnnotatedNotRequired, emptyOptionalMessage)).isNull();
132140

133141
Message<?> notEmptyMessage = MessageBuilder.withPayload("ABC".getBytes()).build();
134142
assertThat(this.resolver.resolveArgument(this.paramAnnotatedNotRequired, notEmptyMessage)).isEqualTo("ABC");
135143
}
136144

145+
@Test
146+
public void resolveOptionalTarget() throws Exception {
147+
Message<?> message = MessageBuilder.withPayload("ABC".getBytes()).build();
148+
Object actual = this.resolver.resolveArgument(paramOptional, message);
149+
150+
assertThat(((Optional<?>) actual).get()).isEqualTo("ABC");
151+
}
152+
153+
@Test
154+
public void resolveOptionalSource() throws Exception {
155+
Message<?> message = MessageBuilder.withPayload(Optional.of("ABC".getBytes())).build();
156+
Object actual = this.resolver.resolveArgument(paramAnnotated, message);
157+
158+
assertThat(actual).isEqualTo("ABC");
159+
}
160+
137161
@Test
138162
public void resolveNonConvertibleParam() {
139163
Message<?> notEmptyMessage = MessageBuilder.withPayload(123).build();
@@ -218,6 +242,7 @@ private void handleMessage(
218242
@Payload(required=false) String paramNotRequired,
219243
@Payload(required=true) Locale nonConvertibleRequiredParam,
220244
@Payload("foo.bar") String paramWithSpelExpression,
245+
@Payload Optional<String> optionalParam,
221246
@MyValid @Payload String validParam,
222247
@Validated String validParamNotAnnotated,
223248
String paramNotAnnotated) {

0 commit comments

Comments
 (0)