Resolvers for destination vars and headers

See gh-21987
This commit is contained in:
Rossen Stoyanchev
2019-01-28 16:39:58 -05:00
parent dda40c1516
commit 567c559da8
10 changed files with 1123 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.handler.annotation;
import java.util.function.Predicate;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
/**
* Predicates for messaging annotations.
*
* @author Rossen Stoyanchev
*/
public class MessagingPredicates {
public static DestinationVariablePredicate destinationVar() {
return new DestinationVariablePredicate();
}
public static DestinationVariablePredicate destinationVar(String value) {
return new DestinationVariablePredicate().value(value);
}
public static HeaderPredicate header() {
return new HeaderPredicate();
}
public static HeaderPredicate header(String name) {
return new HeaderPredicate().name(name);
}
public static HeaderPredicate header(String name, String defaultValue) {
return new HeaderPredicate().name(name).defaultValue(defaultValue);
}
public static HeaderPredicate headerPlain() {
return new HeaderPredicate().noAttributes();
}
public static class DestinationVariablePredicate implements Predicate<MethodParameter> {
@Nullable
private String value;
public DestinationVariablePredicate value(@Nullable String name) {
this.value = name;
return this;
}
public DestinationVariablePredicate noValue() {
this.value = "";
return this;
}
@Override
public boolean test(MethodParameter parameter) {
DestinationVariable annotation = parameter.getParameterAnnotation(DestinationVariable.class);
return annotation != null && (this.value == null || annotation.value().equals(this.value));
}
}
public static class HeaderPredicate implements Predicate<MethodParameter> {
@Nullable
private String name;
@Nullable
private Boolean required;
@Nullable
private String defaultValue;
public HeaderPredicate name(@Nullable String name) {
this.name = name;
return this;
}
public HeaderPredicate noName() {
this.name = "";
return this;
}
public HeaderPredicate required(boolean required) {
this.required = required;
return this;
}
public HeaderPredicate defaultValue(@Nullable String value) {
this.defaultValue = value;
return this;
}
public HeaderPredicate noAttributes() {
this.name = "";
this.required = true;
this.defaultValue = ValueConstants.DEFAULT_NONE;
return this;
}
@Override
public boolean test(MethodParameter parameter) {
Header annotation = parameter.getParameterAnnotation(Header.class);
return annotation != null &&
(this.name == null || annotation.name().equals(this.name)) &&
(this.required == null || annotation.required() == this.required) &&
(this.defaultValue == null || annotation.defaultValue().equals(this.defaultValue));
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.handler.annotation.support.reactive;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.invocation.ResolvableMethod;
import org.springframework.messaging.support.MessageBuilder;
import static org.junit.Assert.*;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.*;
/**
* Test fixture for {@link DestinationVariableMethodArgumentResolver} tests.
* @author Rossen Stoyanchev
*/
public class DestinationVariableMethodArgumentResolverTests {
private final DestinationVariableMethodArgumentResolver resolver =
new DestinationVariableMethodArgumentResolver(new DefaultConversionService());
private final ResolvableMethod resolvable =
ResolvableMethod.on(getClass()).named("handleMessage").build();
@Test
public void supportsParameter() {
assertTrue(resolver.supportsParameter(this.resolvable.annot(destinationVar().noValue()).arg()));
assertFalse(resolver.supportsParameter(this.resolvable.annotNotPresent(DestinationVariable.class).arg()));
}
@Test
public void resolveArgument() {
Map<String, Object> vars = new HashMap<>();
vars.put("foo", "bar");
vars.put("name", "value");
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader(
DestinationVariableMethodArgumentResolver.DESTINATION_TEMPLATE_VARIABLES_HEADER, vars).build();
Object result = resolveArgument(this.resolvable.annot(destinationVar().noValue()).arg(), message);
assertEquals("bar", result);
result = resolveArgument(this.resolvable.annot(destinationVar("name")).arg(), message);
assertEquals("value", result);
}
@Test(expected = MessageHandlingException.class)
public void resolveArgumentNotFound() {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
resolveArgument(this.resolvable.annot(destinationVar().noValue()).arg(), message);
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
private <T> T resolveArgument(MethodParameter param, Message<?> message) {
return (T) this.resolver.resolveArgument(param, message).block(Duration.ofSeconds(5));
}
@SuppressWarnings("unused")
private void handleMessage(
@DestinationVariable String foo,
@DestinationVariable(value = "name") String param1,
String param3) {
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.handler.annotation.support.reactive;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandlingException;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.invocation.ResolvableMethod;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import static org.junit.Assert.*;
import static org.springframework.messaging.handler.annotation.MessagingPredicates.*;
/**
* Test fixture for {@link HeaderMethodArgumentResolver} tests.
* @author Rossen Stoyanchev
*/
public class HeaderMethodArgumentResolverTests {
private HeaderMethodArgumentResolver resolver;
private final ResolvableMethod resolvable = ResolvableMethod.on(getClass()).named("handleMessage").build();
@Before
public void setup() {
GenericApplicationContext context = new GenericApplicationContext();
context.refresh();
this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), context.getBeanFactory());
}
@Test
public void supportsParameter() {
assertTrue(this.resolver.supportsParameter(this.resolvable.annot(headerPlain()).arg()));
assertFalse(this.resolver.supportsParameter(this.resolvable.annotNotPresent(Header.class).arg()));
}
@Test
public void resolveArgument() {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build();
Object result = resolveArgument(this.resolvable.annot(headerPlain()).arg(), message);
assertEquals("foo", result);
}
@Test // SPR-11326
public void resolveArgumentNativeHeader() {
TestMessageHeaderAccessor headers = new TestMessageHeaderAccessor();
headers.setNativeHeader("param1", "foo");
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
assertEquals("foo", resolveArgument(this.resolvable.annot(headerPlain()).arg(), message));
}
@Test
public void resolveArgumentNativeHeaderAmbiguity() {
TestMessageHeaderAccessor headers = new TestMessageHeaderAccessor();
headers.setHeader("param1", "foo");
headers.setNativeHeader("param1", "native-foo");
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
assertEquals("foo", resolveArgument(
this.resolvable.annot(headerPlain()).arg(), message));
assertEquals("native-foo", resolveArgument(
this.resolvable.annot(header("nativeHeaders.param1")).arg(), message));
}
@Test(expected = MessageHandlingException.class)
public void resolveArgumentNotFound() {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
resolveArgument(this.resolvable.annot(headerPlain()).arg(), message);
}
@Test
public void resolveArgumentDefaultValue() {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
Object result = resolveArgument(this.resolvable.annot(header("name", "bar")).arg(), message);
assertEquals("bar", result);
}
@Test
public void resolveDefaultValueSystemProperty() {
System.setProperty("systemProperty", "sysbar");
try {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
MethodParameter param = this.resolvable.annot(header("name", "#{systemProperties.systemProperty}")).arg();
Object result = resolveArgument(param, message);
assertEquals("sysbar", result);
}
finally {
System.clearProperty("systemProperty");
}
}
@Test
public void resolveNameFromSystemProperty() {
System.setProperty("systemProperty", "sysbar");
try {
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("sysbar", "foo").build();
MethodParameter param = this.resolvable.annot(header("#{systemProperties.systemProperty}")).arg();
Object result = resolveArgument(param, message);
assertEquals("foo", result);
}
finally {
System.clearProperty("systemProperty");
}
}
@Test
public void resolveOptionalHeaderWithValue() {
Message<String> message = MessageBuilder.withPayload("foo").setHeader("foo", "bar").build();
MethodParameter param = this.resolvable.annot(header("foo")).arg(Optional.class, String.class);
Object result = resolveArgument(param, message);
assertEquals(Optional.of("bar"), result);
}
@Test
public void resolveOptionalHeaderAsEmpty() {
Message<String> message = MessageBuilder.withPayload("foo").build();
MethodParameter param = this.resolvable.annot(header("foo")).arg(Optional.class, String.class);
Object result = resolveArgument(param, message);
assertEquals(Optional.empty(), result);
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
private <T> T resolveArgument(MethodParameter param, Message<?> message) {
return (T) this.resolver.resolveArgument(param, message).block(Duration.ofSeconds(5));
}
@SuppressWarnings({"unused", "OptionalUsedAsFieldOrParameterType"})
public void handleMessage(
@Header String param1,
@Header(name = "name", defaultValue = "bar") String param2,
@Header(name = "name", defaultValue = "#{systemProperties.systemProperty}") String param3,
@Header(name = "#{systemProperties.systemProperty}") String param4,
String param5,
@Header("foo") Optional<String> param6,
@Header("nativeHeaders.param1") String nativeHeaderParam1) {
}
public static class TestMessageHeaderAccessor extends NativeMessageHeaderAccessor {
TestMessageHeaderAccessor() {
super((Map<String, List<String>>) null);
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.messaging.handler.annotation.support.reactive;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.invocation.ResolvableMethod;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
import static org.junit.Assert.*;
/**
* Test fixture for {@link HeadersMethodArgumentResolver} tests.
* @author Rossen Stoyanchev
*/
public class HeadersMethodArgumentResolverTests {
private final HeadersMethodArgumentResolver resolver = new HeadersMethodArgumentResolver();
private Message<byte[]> message =
MessageBuilder.withPayload(new byte[0]).copyHeaders(Collections.singletonMap("foo", "bar")).build();
private final ResolvableMethod resolvable = ResolvableMethod.on(getClass()).named("handleMessage").build();
@Test
public void supportsParameter() {
assertTrue(this.resolver.supportsParameter(
this.resolvable.annotPresent(Headers.class).arg(Map.class, String.class, Object.class)));
assertTrue(this.resolver.supportsParameter(this.resolvable.arg(MessageHeaders.class)));
assertTrue(this.resolver.supportsParameter(this.resolvable.arg(MessageHeaderAccessor.class)));
assertTrue(this.resolver.supportsParameter(this.resolvable.arg(TestMessageHeaderAccessor.class)));
assertFalse(this.resolver.supportsParameter(this.resolvable.annotPresent(Headers.class).arg(String.class)));
}
@Test
@SuppressWarnings("unchecked")
public void resolveArgumentAnnotated() {
MethodParameter param = this.resolvable.annotPresent(Headers.class).arg(Map.class, String.class, Object.class);
Map<String, Object> headers = resolveArgument(param);
assertEquals("bar", headers.get("foo"));
}
@Test(expected = IllegalStateException.class)
public void resolveArgumentAnnotatedNotMap() {
resolveArgument(this.resolvable.annotPresent(Headers.class).arg(String.class));
}
@Test
public void resolveArgumentMessageHeaders() {
MessageHeaders headers = resolveArgument(this.resolvable.arg(MessageHeaders.class));
assertEquals("bar", headers.get("foo"));
}
@Test
public void resolveArgumentMessageHeaderAccessor() {
MessageHeaderAccessor headers = resolveArgument(this.resolvable.arg(MessageHeaderAccessor.class));
assertEquals("bar", headers.getHeader("foo"));
}
@Test
public void resolveArgumentMessageHeaderAccessorSubclass() {
TestMessageHeaderAccessor headers = resolveArgument(this.resolvable.arg(TestMessageHeaderAccessor.class));
assertEquals("bar", headers.getHeader("foo"));
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
private <T> T resolveArgument(MethodParameter param) {
return (T) this.resolver.resolveArgument(param, this.message).block(Duration.ofSeconds(5));
}
@SuppressWarnings("unused")
private void handleMessage(
@Headers Map<String, Object> param1,
@Headers String param2,
MessageHeaders param3,
MessageHeaderAccessor param4,
TestMessageHeaderAccessor param5) {
}
public static class TestMessageHeaderAccessor extends NativeMessageHeaderAccessor {
TestMessageHeaderAccessor(Message<?> message) {
super(message);
}
public static TestMessageHeaderAccessor wrap(Message<?> message) {
return new TestMessageHeaderAccessor(message);
}
}
}