Move JmsHandlerMethodFactory to spring-messaging
This commit moves JmsHandlerMethodFactory and its default implementation to the messaging abstraction. Working on a similar support for AMQP revealed that this factory has nothing that is JMS specific and is exactly identical in the case of AMQP. Issue: SPR-12053
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.messaging.converter.GenericMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolverComposite;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* The default {@link MessageHandlerMethodFactory} implementation creating an
|
||||
* {@link InvocableHandlerMethod} with the necessary
|
||||
* {@link HandlerMethodArgumentResolver} instances to detect and process
|
||||
* most of the use cases defined by
|
||||
* {@link org.springframework.messaging.handler.annotation.MessageMapping MessageMapping}
|
||||
*
|
||||
* <p>Extra method argument resolvers can be added to customize the method
|
||||
* signature that can be handled.
|
||||
*
|
||||
* <p>By default, the validation process redirects to a no-op implementation, see
|
||||
* {@link #setValidator(Validator)} to customize it. The {@link ConversionService}
|
||||
* can be customized in a similar manner to tune how the message payload
|
||||
* can be converted
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.1
|
||||
* @see #setConversionService
|
||||
* @see #setValidator
|
||||
* @see #setCustomArgumentResolvers
|
||||
*/
|
||||
public class DefaultMessageHandlerMethodFactory implements MessageHandlerMethodFactory, BeanFactoryAware, InitializingBean {
|
||||
|
||||
private ConversionService conversionService = new DefaultFormattingConversionService();
|
||||
|
||||
private MessageConverter messageConverter;
|
||||
|
||||
private Validator validator = new NoOpValidator();
|
||||
|
||||
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
|
||||
|
||||
private final HandlerMethodArgumentResolverComposite argumentResolvers =
|
||||
new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
|
||||
/**
|
||||
* Set the {@link ConversionService} to use to convert the original
|
||||
* message payload or headers.
|
||||
* @see HeaderMethodArgumentResolver
|
||||
* @see GenericMessageConverter
|
||||
*/
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MessageConverter} to use. By default a {@link GenericMessageConverter}
|
||||
* is used.
|
||||
* @see GenericMessageConverter
|
||||
*/
|
||||
public void setMessageConverter(MessageConverter messageConverter) {
|
||||
this.messageConverter = messageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Validator instance used for validating @Payload arguments
|
||||
* @see org.springframework.validation.annotation.Validated
|
||||
* @see org.springframework.messaging.handler.annotation.support.PayloadArgumentResolver
|
||||
*/
|
||||
public void setValidator(Validator validator) {
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of custom {@code HandlerMethodArgumentResolver}s that will be used
|
||||
* after resolvers for supported argument type.
|
||||
* @param customArgumentResolvers the list of resolvers (never {@code null})
|
||||
*/
|
||||
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> customArgumentResolvers) {
|
||||
this.customArgumentResolvers = customArgumentResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the complete list of supported argument types effectively overriding
|
||||
* the ones configured by default. This is an advanced option. For most use cases
|
||||
* it should be sufficient to use {@link #setCustomArgumentResolvers(java.util.List)}.
|
||||
*/
|
||||
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
if (argumentResolvers == null) {
|
||||
this.argumentResolvers.clear();
|
||||
return;
|
||||
}
|
||||
this.argumentResolvers.addResolvers(argumentResolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BeanFactory} only needs to be available for placeholder resolution
|
||||
* in handler method arguments; it's optional otherwise.
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
if (this.messageConverter == null) {
|
||||
this.messageConverter = new GenericMessageConverter(this.conversionService);
|
||||
}
|
||||
if (this.argumentResolvers.getResolvers().isEmpty()) {
|
||||
this.argumentResolvers.addResolvers(initArgumentResolvers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method) {
|
||||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(bean, method);
|
||||
handlerMethod.setMessageMethodArgumentResolvers(argumentResolvers);
|
||||
return handlerMethod;
|
||||
}
|
||||
|
||||
protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
|
||||
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
|
||||
ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory ?
|
||||
(ConfigurableBeanFactory) this.beanFactory : null);
|
||||
|
||||
// Annotation-based argument resolution
|
||||
resolvers.add(new HeaderMethodArgumentResolver(this.conversionService, cbf));
|
||||
resolvers.add(new HeadersMethodArgumentResolver());
|
||||
|
||||
// Type-based argument resolution
|
||||
resolvers.add(new MessageMethodArgumentResolver());
|
||||
|
||||
if (this.customArgumentResolvers != null) {
|
||||
resolvers.addAll(this.customArgumentResolvers);
|
||||
}
|
||||
resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator));
|
||||
|
||||
return resolvers;
|
||||
}
|
||||
|
||||
|
||||
private static final class NoOpValidator implements Validator {
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
|
||||
/**
|
||||
* A factory for {@link InvocableHandlerMethod} that is suitable to process
|
||||
* an incoming {@link org.springframework.messaging.Message}
|
||||
*
|
||||
* <p>Typically used by listener endpoints that require a flexible method
|
||||
* signature.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.1
|
||||
*/
|
||||
public interface MessageHandlerMethodFactory {
|
||||
|
||||
/**
|
||||
* Create the {@link InvocableHandlerMethod} that is able to process the specified
|
||||
* method endpoint.
|
||||
* @param bean the bean instance
|
||||
* @param method the method to invoke
|
||||
* @return an {@link InvocableHandlerMethod} suitable for that method
|
||||
*/
|
||||
InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2002-2014 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;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.rules.TestName;
|
||||
|
||||
import org.springframework.beans.factory.support.StaticListableBeanFactory;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.GenericConversionService;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConversionException;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class DefaultMessageHandlerMethodFactoryTests {
|
||||
|
||||
@Rule
|
||||
public final TestName name = new TestName();
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private final SampleBean sample = new SampleBean();
|
||||
|
||||
@Test
|
||||
public void customConversion() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
conversionService.addConverter(SampleBean.class, String.class, new Converter<SampleBean, String>() {
|
||||
@Override
|
||||
public String convert(SampleBean source) {
|
||||
return "foo bar";
|
||||
}
|
||||
});
|
||||
instance.setConversionService(conversionService);
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "simpleString", String.class);
|
||||
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload(sample).build());
|
||||
assertMethodInvocation(sample, "simpleString");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customConversionServiceFailure() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
GenericConversionService conversionService = new GenericConversionService();
|
||||
assertFalse("conversion service should fail to convert payload",
|
||||
conversionService.canConvert(Integer.class, String.class));
|
||||
instance.setConversionService(conversionService);
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "simpleString", String.class);
|
||||
|
||||
thrown.expect(MessageConversionException.class);
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customMessageConverterFailure() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
MessageConverter messageConverter = new ByteArrayMessageConverter();
|
||||
instance.setMessageConverter(messageConverter);
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "simpleString", String.class);
|
||||
|
||||
thrown.expect(MessageConversionException.class);
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customArgumentResolver() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
|
||||
customResolvers.add(new CustomHandlerMethodArgumentResolver());
|
||||
instance.setCustomArgumentResolvers(customResolvers);
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "customArgumentResolver", Locale.class);
|
||||
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload(123).build());
|
||||
assertMethodInvocation(sample, "customArgumentResolver");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideArgumentResolvers() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
List<HandlerMethodArgumentResolver> customResolvers = new ArrayList<HandlerMethodArgumentResolver>();
|
||||
customResolvers.add(new CustomHandlerMethodArgumentResolver());
|
||||
instance.setArgumentResolvers(customResolvers);
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
Message<String> message = MessageBuilder.withPayload("sample").build();
|
||||
|
||||
// This will work as the local resolver is set
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "customArgumentResolver", Locale.class);
|
||||
invocableHandlerMethod.invoke(message);
|
||||
assertMethodInvocation(sample, "customArgumentResolver");
|
||||
|
||||
// This won't work as no resolver is known for the payload
|
||||
InvocableHandlerMethod invocableHandlerMethod2 =
|
||||
createInvocableHandlerMethod(instance, "simpleString", String.class);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("No suitable resolver for");
|
||||
invocableHandlerMethod2.invoke(message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noValidationByDefault() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "payloadValidation", String.class);
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload("failure").build());
|
||||
assertMethodInvocation(sample, "payloadValidation");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customValidation() throws Exception {
|
||||
DefaultMessageHandlerMethodFactory instance = createInstance();
|
||||
instance.setValidator(new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
String value = (String) target;
|
||||
if ("failure".equals(value)) {
|
||||
errors.reject("not a valid value");
|
||||
}
|
||||
}
|
||||
});
|
||||
instance.afterPropertiesSet();
|
||||
|
||||
InvocableHandlerMethod invocableHandlerMethod =
|
||||
createInvocableHandlerMethod(instance, "payloadValidation", String.class);
|
||||
thrown.expect(MethodArgumentNotValidException.class);
|
||||
invocableHandlerMethod.invoke(MessageBuilder.withPayload("failure").build());
|
||||
}
|
||||
|
||||
|
||||
private void assertMethodInvocation(SampleBean bean, String methodName) {
|
||||
assertTrue("Method " + methodName + " should have been invoked", bean.invocations.get(methodName));
|
||||
}
|
||||
|
||||
private InvocableHandlerMethod createInvocableHandlerMethod(
|
||||
DefaultMessageHandlerMethodFactory factory, String methodName, Class<?>... parameterTypes) {
|
||||
return factory.createInvocableHandlerMethod(sample, getListenerMethod(methodName, parameterTypes));
|
||||
}
|
||||
|
||||
private DefaultMessageHandlerMethodFactory createInstance() {
|
||||
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
|
||||
factory.setBeanFactory(new StaticListableBeanFactory());
|
||||
return factory;
|
||||
}
|
||||
|
||||
private Method getListenerMethod(String methodName, Class<?>... parameterTypes) {
|
||||
Method method = ReflectionUtils.findMethod(SampleBean.class, methodName, parameterTypes);
|
||||
assertNotNull("no method found with name " + methodName + " and parameters " + Arrays.toString(parameterTypes));
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
static class SampleBean {
|
||||
|
||||
private final Map<String, Boolean> invocations = new HashMap<String, Boolean>();
|
||||
|
||||
public void simpleString(String value) {
|
||||
invocations.put("simpleString", true);
|
||||
}
|
||||
|
||||
public void payloadValidation(@Payload @Valid String value) {
|
||||
invocations.put("payloadValidation", true);
|
||||
}
|
||||
|
||||
public void customArgumentResolver(Locale locale) {
|
||||
invocations.put("customArgumentResolver", true);
|
||||
assertEquals("Wrong value for locale", Locale.getDefault(), locale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class CustomHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return parameter.getParameterType().isAssignableFrom(Locale.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user