Add @ReplyTo/@ReplyToUser, remove deps on spring-web
This commit is contained in:
@@ -32,10 +32,9 @@ import java.lang.annotation.Target;
|
||||
@Documented
|
||||
public @interface ReplyTo {
|
||||
|
||||
|
||||
/**
|
||||
* The destination value for the reply.
|
||||
* The destination for a message created from the return value of a method.
|
||||
*/
|
||||
String value();
|
||||
String[] value() default {};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.core.ExceptionDepthComparator;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodSelector;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
|
||||
/**
|
||||
* Discovers annotated exception handling methods in a given class type, including all
|
||||
* super types, and helps to resolve an Exception to a method that can handle it. The
|
||||
* exception types supported by a given method can also be discovered from the method
|
||||
* signature.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ExceptionHandlerMethodResolver {
|
||||
|
||||
private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis");
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> mappedMethods =
|
||||
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
|
||||
|
||||
private final Map<Class<? extends Throwable>, Method> exceptionLookupCache =
|
||||
new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);
|
||||
|
||||
|
||||
/**
|
||||
* A constructor that finds {@link MessageExceptionHandler} methods in the given type.
|
||||
* @param handlerType the type to introspect
|
||||
*/
|
||||
public ExceptionHandlerMethodResolver(Class<?> handlerType) {
|
||||
for (Method method : HandlerMethodSelector.selectMethods(handlerType, EXCEPTION_HANDLER_METHOD_FILTER)) {
|
||||
for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
|
||||
addExceptionMapping(exceptionType, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract exception mappings from the {@code @ExceptionHandler} annotation
|
||||
* first and as a fall-back from the method signature.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
|
||||
List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
|
||||
|
||||
detectAnnotationExceptionMappings(method, result);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
for (Class<?> paramType : method.getParameterTypes()) {
|
||||
if (Throwable.class.isAssignableFrom(paramType)) {
|
||||
result.add((Class<? extends Throwable>) paramType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
|
||||
MessageExceptionHandler annot = AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class);
|
||||
result.addAll(Arrays.asList(annot.value()));
|
||||
}
|
||||
|
||||
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
|
||||
Method oldMethod = this.mappedMethods.put(exceptionType, method);
|
||||
if (oldMethod != null && !oldMethod.equals(method)) {
|
||||
throw new IllegalStateException(
|
||||
"Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" +
|
||||
oldMethod + ", " + method + "}.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the contained type has any exception mappings.
|
||||
*/
|
||||
public boolean hasExceptionMappings() {
|
||||
return (this.mappedMethods.size() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a method to handle the given exception.
|
||||
* Use {@link ExceptionDepthComparator} if more than one match is found.
|
||||
* @param exception the exception
|
||||
* @return a method to handle the exception or {@code null}
|
||||
*/
|
||||
public Method resolveMethod(Exception exception) {
|
||||
Class<? extends Exception> exceptionType = exception.getClass();
|
||||
Method method = this.exceptionLookupCache.get(exceptionType);
|
||||
if (method == null) {
|
||||
method = getMappedMethod(exceptionType);
|
||||
this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND);
|
||||
}
|
||||
return method != NO_METHOD_FOUND ? method : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method mapped to the given exception type or {@code null}.
|
||||
*/
|
||||
private Method getMappedMethod(Class<? extends Exception> exceptionType) {
|
||||
List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();
|
||||
for(Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
|
||||
if (mappedException.isAssignableFrom(exceptionType)) {
|
||||
matches.add(mappedException);
|
||||
}
|
||||
}
|
||||
if (!matches.isEmpty()) {
|
||||
Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
|
||||
return mappedMethods.get(matches.get(0));
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** A filter for selecting annotated exception handling methods. */
|
||||
public final static MethodFilter EXCEPTION_HANDLER_METHOD_FILTER = new MethodFilter() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
return AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class) != null;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -19,21 +19,26 @@ package org.springframework.messaging.handler.annotation.support;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.MessageBody;
|
||||
import org.springframework.messaging.handler.method.MessageArgumentResolver;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.messaging.support.converter.MessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* <p>This {@link HandlerMethodArgumentResolver} should be ordered last as it supports all
|
||||
* types and does not require the {@link MessageBody} annotation.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageBodyArgumentResolver implements MessageArgumentResolver {
|
||||
public class MessageBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final MessageConverter<?> converter;
|
||||
|
||||
|
||||
public MessageBodyArgumentResolver(MessageConverter<?> converter) {
|
||||
public MessageBodyMethodArgumentResolver(MessageConverter<?> converter) {
|
||||
Assert.notNull(converter, "converter is required");
|
||||
this.converter = converter;
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageExceptionHandlerMethodResolver extends ExceptionHandlerMethodResolver {
|
||||
|
||||
|
||||
public MessageExceptionHandlerMethodResolver(Class<?> handlerType) {
|
||||
super(handlerType);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected MethodFilter getExceptionHandlerMethods() {
|
||||
return MESSAGE_EXCEPTION_HANDLER_METHODS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
|
||||
MessageExceptionHandler annotation = AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class);
|
||||
result.addAll(Arrays.asList(annotation.value()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A filter for selecting {@code @ExceptionHandler} methods.
|
||||
*/
|
||||
public final static MethodFilter MESSAGE_EXCEPTION_HANDLER_METHODS = new MethodFilter() {
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
return AnnotationUtils.findAnnotation(method, MessageExceptionHandler.class) != null;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright 2002-2012 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.method;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Encapsulates information about a bean method consisting of a
|
||||
* {@linkplain #getMethod() method} and a {@linkplain #getBean() bean}. Provides
|
||||
* convenient access to method parameters, the method return value, method
|
||||
* annotations.
|
||||
*
|
||||
* <p>The class may be created with a bean instance or with a bean name (e.g. lazy
|
||||
* bean, prototype bean). Use {@link #createWithResolvedBean()} to obtain an
|
||||
* {@link HandlerMethod} instance with a bean instance initialized through the
|
||||
* bean factory.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class HandlerMethod {
|
||||
|
||||
/** Logger that is available to subclasses */
|
||||
protected final Log logger = LogFactory.getLog(HandlerMethod.class);
|
||||
|
||||
private final Object bean;
|
||||
|
||||
private final Method method;
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final MethodParameter[] parameters;
|
||||
|
||||
private final Method bridgedMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a bean instance and a method.
|
||||
*/
|
||||
public HandlerMethod(Object bean, Method method) {
|
||||
Assert.notNull(bean, "bean is required");
|
||||
Assert.notNull(method, "method is required");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
private MethodParameter[] initMethodParameters() {
|
||||
int count = this.bridgedMethod.getParameterTypes().length;
|
||||
MethodParameter[] result = new MethodParameter[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
result[i] = new HandlerMethodParameter(i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a bean instance, method name, and parameter types.
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
Assert.notNull(bean, "bean is required");
|
||||
Assert.notNull(methodName, "method is required");
|
||||
this.bean = bean;
|
||||
this.beanFactory = null;
|
||||
this.method = bean.getClass().getMethod(methodName, parameterTypes);
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a bean name, a method, and a {@code BeanFactory}.
|
||||
* The method {@link #createWithResolvedBean()} may be used later to
|
||||
* re-create the {@code HandlerMethod} with an initialized the bean.
|
||||
*/
|
||||
public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
|
||||
Assert.hasText(beanName, "beanName is required");
|
||||
Assert.notNull(beanFactory, "beanFactory is required");
|
||||
Assert.notNull(method, "method is required");
|
||||
Assert.isTrue(beanFactory.containsBean(beanName),
|
||||
"Bean factory [" + beanFactory + "] does not contain bean [" + beanName + "]");
|
||||
this.bean = beanName;
|
||||
this.beanFactory = beanFactory;
|
||||
this.method = method;
|
||||
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
|
||||
this.parameters = initMethodParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor for use in sub-classes.
|
||||
*/
|
||||
protected HandlerMethod(HandlerMethod handlerMethod) {
|
||||
Assert.notNull(handlerMethod, "HandlerMethod is required");
|
||||
this.bean = handlerMethod.bean;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-create HandlerMethod with the resolved handler.
|
||||
*/
|
||||
private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
|
||||
Assert.notNull(handlerMethod, "handlerMethod is required");
|
||||
Assert.notNull(handler, "handler is required");
|
||||
this.bean = handler;
|
||||
this.beanFactory = handlerMethod.beanFactory;
|
||||
this.method = handlerMethod.method;
|
||||
this.bridgedMethod = handlerMethod.bridgedMethod;
|
||||
this.parameters = handlerMethod.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bean for this handler method.
|
||||
*/
|
||||
public Object getBean() {
|
||||
return this.bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method for this handler method.
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the handler for this handler method.
|
||||
* Note that if the bean type is a CGLIB-generated class, the original, user-defined class is returned.
|
||||
*/
|
||||
public Class<?> getBeanType() {
|
||||
Class<?> clazz = (this.bean instanceof String)
|
||||
? this.beanFactory.getType((String) this.bean) : this.bean.getClass();
|
||||
|
||||
return ClassUtils.getUserClass(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the bean method is a bridge method, this method returns the bridged (user-defined) method.
|
||||
* Otherwise it returns the same method as {@link #getMethod()}.
|
||||
*/
|
||||
protected Method getBridgedMethod() {
|
||||
return this.bridgedMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method parameters for this handler method.
|
||||
*/
|
||||
public MethodParameter[] getMethodParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HandlerMethod return type.
|
||||
*/
|
||||
public MethodParameter getReturnType() {
|
||||
return new HandlerMethodParameter(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual return value type.
|
||||
*/
|
||||
public MethodParameter getReturnValueType(Object returnValue) {
|
||||
return new ReturnValueMethodParameter(returnValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the method return type is void, {@code false} otherwise.
|
||||
*/
|
||||
public boolean isVoid() {
|
||||
return Void.TYPE.equals(getReturnType().getParameterType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single annotation on the underlying method traversing its super methods if no
|
||||
* annotation can be found on the given method itself.
|
||||
* @param annotationType the type of annotation to introspect the method for.
|
||||
* @return the annotation, or {@code null} if none found
|
||||
*/
|
||||
public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
|
||||
return AnnotationUtils.findAnnotation(this.bridgedMethod, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the provided instance contains a bean name rather than an object instance, the bean name is resolved
|
||||
* before a {@link HandlerMethod} is created and returned.
|
||||
*/
|
||||
public HandlerMethod createWithResolvedBean() {
|
||||
Object handler = this.bean;
|
||||
if (this.bean instanceof String) {
|
||||
String beanName = (String) this.bean;
|
||||
handler = this.beanFactory.getBean(beanName);
|
||||
}
|
||||
return new HandlerMethod(this, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o != null && o instanceof HandlerMethod) {
|
||||
HandlerMethod other = (HandlerMethod) o;
|
||||
return this.bean.equals(other.bean) && this.method.equals(other.method);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * this.bean.hashCode() + this.method.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return method.toGenericString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A MethodParameter with HandlerMethod-specific behavior.
|
||||
*/
|
||||
private class HandlerMethodParameter extends MethodParameter {
|
||||
|
||||
protected HandlerMethodParameter(int index) {
|
||||
super(HandlerMethod.this.bridgedMethod, index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getDeclaringClass() {
|
||||
return HandlerMethod.this.getBeanType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Annotation> T getMethodAnnotation(Class<T> annotationType) {
|
||||
return HandlerMethod.this.getMethodAnnotation(annotationType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A MethodParameter for a HandlerMethod return type based on an actual return value.
|
||||
*/
|
||||
private class ReturnValueMethodParameter extends HandlerMethodParameter {
|
||||
|
||||
private final Object returnValue;
|
||||
|
||||
public ReturnValueMethodParameter(Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
return (this.returnValue != null) ? this.returnValue.getClass() : super.getParameterType();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.springframework.messaging.Message;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface MessageArgumentResolver {
|
||||
public interface HandlerMethodArgumentResolver {
|
||||
|
||||
/**
|
||||
* Whether the given {@linkplain MethodParameter method parameter} is
|
||||
@@ -30,32 +30,32 @@ import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Resolves method parameters by delegating to a list of registered
|
||||
* {@link MessageArgumentResolver}. Previously resolved method parameters are cached
|
||||
* {@link HandlerMethodArgumentResolver}. Previously resolved method parameters are cached
|
||||
* for faster lookups.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageArgumentResolverComposite implements MessageArgumentResolver {
|
||||
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final List<MessageArgumentResolver> argumentResolvers = new LinkedList<MessageArgumentResolver>();
|
||||
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<HandlerMethodArgumentResolver>();
|
||||
|
||||
private final Map<MethodParameter, MessageArgumentResolver> argumentResolverCache =
|
||||
new ConcurrentHashMap<MethodParameter, MessageArgumentResolver>(256);
|
||||
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
|
||||
new ConcurrentHashMap<MethodParameter, HandlerMethodArgumentResolver>(256);
|
||||
|
||||
|
||||
/**
|
||||
* Return a read-only list with the contained resolvers, or an empty list.
|
||||
*/
|
||||
public List<MessageArgumentResolver> getResolvers() {
|
||||
public List<HandlerMethodArgumentResolver> getResolvers() {
|
||||
return Collections.unmodifiableList(this.argumentResolvers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the given {@linkplain MethodParameter method parameter} is supported by any registered
|
||||
* {@link MessageArgumentResolver}.
|
||||
* {@link HandlerMethodArgumentResolver}.
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
@@ -63,24 +63,24 @@ public class MessageArgumentResolverComposite implements MessageArgumentResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over registered {@link MessageArgumentResolver}s and invoke the one that supports it.
|
||||
* @exception IllegalStateException if no suitable {@link MessageArgumentResolver} is found.
|
||||
* Iterate over registered {@link HandlerMethodArgumentResolver}s and invoke the one that supports it.
|
||||
* @exception IllegalStateException if no suitable {@link HandlerMethodArgumentResolver} is found.
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception {
|
||||
|
||||
MessageArgumentResolver resolver = getArgumentResolver(parameter);
|
||||
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
|
||||
Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
|
||||
return resolver.resolveArgument(parameter, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a registered {@link MessageArgumentResolver} that supports the given method parameter.
|
||||
* Find a registered {@link HandlerMethodArgumentResolver} that supports the given method parameter.
|
||||
*/
|
||||
private MessageArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
MessageArgumentResolver result = this.argumentResolverCache.get(parameter);
|
||||
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
|
||||
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
|
||||
if (result == null) {
|
||||
for (MessageArgumentResolver resolver : this.argumentResolvers) {
|
||||
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
|
||||
if (resolver.supportsParameter(parameter)) {
|
||||
result = resolver;
|
||||
this.argumentResolverCache.put(parameter, result);
|
||||
@@ -92,19 +92,19 @@ public class MessageArgumentResolverComposite implements MessageArgumentResolver
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link MessageArgumentResolver}.
|
||||
* Add the given {@link HandlerMethodArgumentResolver}.
|
||||
*/
|
||||
public MessageArgumentResolverComposite addResolver(MessageArgumentResolver argumentResolver) {
|
||||
public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver argumentResolver) {
|
||||
this.argumentResolvers.add(argumentResolver);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link MessageArgumentResolver}s.
|
||||
* Add the given {@link HandlerMethodArgumentResolver}s.
|
||||
*/
|
||||
public MessageArgumentResolverComposite addResolvers(List<? extends MessageArgumentResolver> argumentResolvers) {
|
||||
public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
if (argumentResolvers != null) {
|
||||
for (MessageArgumentResolver resolver : argumentResolvers) {
|
||||
for (HandlerMethodArgumentResolver resolver : argumentResolvers) {
|
||||
this.argumentResolvers.add(resolver);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.springframework.messaging.Message;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public interface MessageReturnValueHandler {
|
||||
public interface HandlerMethodReturnValueHandler {
|
||||
|
||||
/**
|
||||
* Whether the given {@linkplain MethodParameter method return type} is
|
||||
@@ -28,25 +28,25 @@ import org.springframework.util.Assert;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class MessageReturnValueHandlerComposite implements MessageReturnValueHandler {
|
||||
public class HandlerMethodReturnValueHandlerComposite implements HandlerMethodReturnValueHandler {
|
||||
|
||||
private final List<MessageReturnValueHandler> returnValueHandlers = new ArrayList<MessageReturnValueHandler>();
|
||||
private final List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<HandlerMethodReturnValueHandler>();
|
||||
|
||||
|
||||
/**
|
||||
* Add the given {@link MessageReturnValueHandler}.
|
||||
* Add the given {@link HandlerMethodReturnValueHandler}.
|
||||
*/
|
||||
public MessageReturnValueHandlerComposite addHandler(MessageReturnValueHandler returnValuehandler) {
|
||||
public HandlerMethodReturnValueHandlerComposite addHandler(HandlerMethodReturnValueHandler returnValuehandler) {
|
||||
this.returnValueHandlers.add(returnValuehandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given {@link MessageReturnValueHandler}s.
|
||||
* Add the given {@link HandlerMethodReturnValueHandler}s.
|
||||
*/
|
||||
public MessageReturnValueHandlerComposite addHandlers(List<? extends MessageReturnValueHandler> handlers) {
|
||||
public HandlerMethodReturnValueHandlerComposite addHandlers(List<? extends HandlerMethodReturnValueHandler> handlers) {
|
||||
if (handlers != null) {
|
||||
for (MessageReturnValueHandler handler : handlers) {
|
||||
for (HandlerMethodReturnValueHandler handler : handlers) {
|
||||
this.returnValueHandlers.add(handler);
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,8 @@ public class MessageReturnValueHandlerComposite implements MessageReturnValueHan
|
||||
return getReturnValueHandler(returnType) != null;
|
||||
}
|
||||
|
||||
private MessageReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
|
||||
for (MessageReturnValueHandler handler : this.returnValueHandlers) {
|
||||
private HandlerMethodReturnValueHandler getReturnValueHandler(MethodParameter returnType) {
|
||||
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
|
||||
if (handler.supportsReturnType(returnType)) {
|
||||
return handler;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class MessageReturnValueHandlerComposite implements MessageReturnValueHan
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
|
||||
throws Exception {
|
||||
|
||||
MessageReturnValueHandler handler = getReturnValueHandler(returnType);
|
||||
HandlerMethodReturnValueHandler handler = getReturnValueHandler(returnType);
|
||||
Assert.notNull(handler, "Unknown return value type [" + returnType.getParameterType().getName() + "]");
|
||||
handler.handleReturnValue(returnValue, returnType, message);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
|
||||
/**
|
||||
* Defines the algorithm for searching handler methods exhaustively including interfaces and parent
|
||||
* classes while also dealing with parameterized methods as well as interface and class-based proxies.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public abstract class HandlerMethodSelector {
|
||||
|
||||
/**
|
||||
* Selects handler methods for the given handler type. Callers of this method define handler methods
|
||||
* of interest through the {@link MethodFilter} parameter.
|
||||
*
|
||||
* @param handlerType the handler type to search handler methods on
|
||||
* @param handlerMethodFilter a {@link MethodFilter} to help recognize handler methods of interest
|
||||
* @return the selected methods, or an empty set
|
||||
*/
|
||||
public static Set<Method> selectMethods(final Class<?> handlerType, final MethodFilter handlerMethodFilter) {
|
||||
final Set<Method> handlerMethods = new LinkedHashSet<Method>();
|
||||
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
|
||||
Class<?> specificHandlerType = null;
|
||||
if (!Proxy.isProxyClass(handlerType)) {
|
||||
handlerTypes.add(handlerType);
|
||||
specificHandlerType = handlerType;
|
||||
}
|
||||
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
|
||||
for (Class<?> currentHandlerType : handlerTypes) {
|
||||
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
|
||||
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) {
|
||||
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
|
||||
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
|
||||
if (handlerMethodFilter.matches(specificMethod) &&
|
||||
(bridgedMethod == specificMethod || !handlerMethodFilter.matches(bridgedMethod))) {
|
||||
handlerMethods.add(specificMethod);
|
||||
}
|
||||
}
|
||||
}, ReflectionUtils.USER_DECLARED_METHODS);
|
||||
}
|
||||
return handlerMethods;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,21 +26,20 @@ import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
|
||||
/**
|
||||
* Invokes the handler method for a given message after resolving
|
||||
* its method argument values through registered {@link MessageArgumentResolver}s.
|
||||
* its method argument values through registered {@link HandlerMethodArgumentResolver}s.
|
||||
* <p>
|
||||
* Use {@link #setMessageMethodArgumentResolvers(MessageArgumentResolverComposite)}
|
||||
* Use {@link #setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite)}
|
||||
* to customize the list of argument resolvers.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class InvocableMessageHandlerMethod extends HandlerMethod {
|
||||
public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private MessageArgumentResolverComposite argumentResolvers = new MessageArgumentResolverComposite();
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
@@ -48,14 +47,14 @@ public class InvocableMessageHandlerMethod extends HandlerMethod {
|
||||
/**
|
||||
* Create an instance from a {@code HandlerMethod}.
|
||||
*/
|
||||
public InvocableMessageHandlerMethod(HandlerMethod handlerMethod) {
|
||||
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
|
||||
super(handlerMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a bean instance and a method.
|
||||
*/
|
||||
public InvocableMessageHandlerMethod(Object bean, Method method) {
|
||||
public InvocableHandlerMethod(Object bean, Method method) {
|
||||
super(bean, method);
|
||||
}
|
||||
|
||||
@@ -68,17 +67,17 @@ public class InvocableMessageHandlerMethod extends HandlerMethod {
|
||||
* @param parameterTypes the method parameter types
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public InvocableMessageHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
|
||||
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
super(bean, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@link MessageArgumentResolver}s to use to use for resolving method
|
||||
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving method
|
||||
* argument values.
|
||||
*/
|
||||
public void setMessageMethodArgumentResolvers(MessageArgumentResolverComposite argumentResolvers) {
|
||||
public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
|
||||
this.argumentResolvers = argumentResolvers;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public class SimpMessageHeaderAccessor extends NativeMessageHeaderAccessor {
|
||||
|
||||
public static final String MESSAGE_TYPE = "messageType";
|
||||
|
||||
// TODO
|
||||
public static final String PROTOCOL_MESSAGE_TYPE = "protocolMessageType";
|
||||
|
||||
public static final String SESSION_ID = "sessionId";
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.messaging.simp;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
@@ -75,11 +77,9 @@ public class SimpMessagingTemplate extends AbstractMessageSendingTemplate<String
|
||||
|
||||
protected <P> Message<P> addDestinationToMessage(Message<P> message, String destination) {
|
||||
Assert.notNull(destination, "destination is required");
|
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
|
||||
headers.copyHeaders(message.getHeaders());
|
||||
headers.setDestination(destination);
|
||||
message = MessageBuilder.withPayload(message.getPayload()).copyHeaders(headers.toMap()).build();
|
||||
return message;
|
||||
return MessageBuilder.fromMessage(message)
|
||||
.setHeader(SimpMessageHeaderAccessor.MESSAGE_TYPE, SimpMessageType.MESSAGE)
|
||||
.setHeader(SimpMessageHeaderAccessor.DESTINATIONS, Arrays.asList(destination)).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.simp.annotation;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/**
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface ReplyToUser {
|
||||
|
||||
/**
|
||||
* The destination for a message based on the return value of a method.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Annotations and support classes for handling messages from simple messaging
|
||||
* protocols (like STOMP).
|
||||
*/
|
||||
package org.springframework.messaging.simp.annotation;
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.simp.annotation.support;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.handler.annotation.ReplyTo;
|
||||
import org.springframework.messaging.handler.method.MessageReturnValueHandler;
|
||||
import org.springframework.messaging.handler.method.MissingSessionUserException;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.converter.MessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Expects return values to be either a {@link Message} or the payload of a message to be
|
||||
* converted and sent on a {@link MessageChannel}.
|
||||
*
|
||||
* <p>This {@link MessageReturnValueHandler} should be ordered last as it supports all
|
||||
* return value types.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class DefaultMessageReturnValueHandler implements MessageReturnValueHandler {
|
||||
|
||||
private MessageChannel inboundChannel;
|
||||
|
||||
private MessageChannel outboundChannel;
|
||||
|
||||
private final MessageConverter converter;
|
||||
|
||||
|
||||
public DefaultMessageReturnValueHandler(MessageChannel inboundChannel, MessageChannel outboundChannel,
|
||||
MessageConverter<?> converter) {
|
||||
|
||||
Assert.notNull(inboundChannel, "inboundChannel is required");
|
||||
Assert.notNull(outboundChannel, "outboundChannel is required");
|
||||
Assert.notNull(converter, "converter is required");
|
||||
|
||||
this.inboundChannel = inboundChannel;
|
||||
this.outboundChannel = outboundChannel;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
|
||||
throws Exception {
|
||||
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(message);
|
||||
|
||||
Message<?> returnMessage = (returnValue instanceof Message) ? (Message<?>) returnValue : null;
|
||||
Object returnPayload = (returnMessage != null) ? returnMessage.getPayload() : returnValue;
|
||||
|
||||
SimpMessageHeaderAccessor returnHeaders = (returnMessage != null) ?
|
||||
SimpMessageHeaderAccessor.wrap(returnMessage) : SimpMessageHeaderAccessor.create();
|
||||
|
||||
returnHeaders.setSessionId(inputHeaders.getSessionId());
|
||||
returnHeaders.setSubscriptionId(inputHeaders.getSubscriptionId());
|
||||
|
||||
String destination = getDestination(message, returnType, inputHeaders, returnHeaders);
|
||||
returnHeaders.setDestination(destination);
|
||||
|
||||
returnMessage = this.converter.toMessage(returnPayload);
|
||||
returnMessage = MessageBuilder.fromMessage(returnMessage).copyHeaders(returnHeaders.toMap()).build();
|
||||
|
||||
if (destination.startsWith("/user/")) {
|
||||
this.inboundChannel.send(returnMessage);
|
||||
}
|
||||
else {
|
||||
this.outboundChannel.send(returnMessage);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getDestination(Message<?> inputMessage, MethodParameter returnType,
|
||||
SimpMessageHeaderAccessor inputHeaders, SimpMessageHeaderAccessor returnHeaders) {
|
||||
|
||||
ReplyTo annot = returnType.getMethodAnnotation(ReplyTo.class);
|
||||
|
||||
if (returnHeaders.getDestination() != null) {
|
||||
return returnHeaders.getDestination();
|
||||
}
|
||||
else if (annot != null) {
|
||||
Principal user = inputHeaders.getUser();
|
||||
if (user == null) {
|
||||
throw new MissingSessionUserException(inputMessage);
|
||||
}
|
||||
return "/user/" + user.getName() + annot.value();
|
||||
}
|
||||
else if (inputHeaders.getDestination() != null) {
|
||||
return inputHeaders.getDestination();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.messaging.handler.method;
|
||||
package org.springframework.messaging.simp.annotation.support;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
@@ -20,8 +20,7 @@ import java.security.Principal;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.method.MessageArgumentResolver;
|
||||
import org.springframework.messaging.handler.method.MissingSessionUserException;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolver;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
|
||||
|
||||
@@ -29,7 +28,7 @@ import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class PrincipalMessageArgumentResolver implements MessageArgumentResolver {
|
||||
public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.simp.annotation.support;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.core.MessagePostProcessor;
|
||||
import org.springframework.messaging.core.MessageSendingOperations;
|
||||
import org.springframework.messaging.handler.annotation.ReplyTo;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.annotation.ReplyToUser;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link HandlerMethodReturnValueHandler} for replying to destinations specified in a
|
||||
* {@link ReplyTo} or {@link ReplyToUser} method-level annotations.
|
||||
* <p>
|
||||
* The value returned from the method is converted, and turned to a {@link Message} and
|
||||
* sent through the provided {@link MessageChannel}. The
|
||||
* message is then enriched with the sessionId of the input message as well as the
|
||||
* destination from the annotation(s). If multiple destinations are specified, a copy of
|
||||
* the message is sent to each destination.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class ReplyToMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
private final MessageSendingOperations<String> messagingTemplate;
|
||||
|
||||
|
||||
public ReplyToMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
|
||||
Assert.notNull(messagingTemplate, "messagingTemplate is required");
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return ((returnType.getMethodAnnotation(ReplyTo.class) != null)
|
||||
|| (returnType.getMethodAnnotation(ReplyToUser.class) != null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> inputMessage)
|
||||
throws Exception {
|
||||
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReplyTo replyTo = returnType.getMethodAnnotation(ReplyTo.class);
|
||||
ReplyToUser replyToUser = returnType.getMethodAnnotation(ReplyToUser.class);
|
||||
|
||||
List<String> destinations = new ArrayList<String>();
|
||||
if (replyTo != null) {
|
||||
destinations.addAll(Arrays.asList(replyTo.value()));
|
||||
}
|
||||
if (replyToUser != null) {
|
||||
Principal user = getUser(inputMessage);
|
||||
for (String destination : replyToUser.value()) {
|
||||
destinations.add("/user/" + user.getName() + destination);
|
||||
}
|
||||
}
|
||||
|
||||
MessagePostProcessor postProcessor = new SessionIdHeaderPostProcessor(inputMessage);
|
||||
|
||||
for (String destination : destinations) {
|
||||
this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
private Principal getUser(Message<?> inputMessage) {
|
||||
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(inputMessage);
|
||||
Principal user = inputHeaders.getUser();
|
||||
if (user == null) {
|
||||
throw new MissingSessionUserException(inputMessage);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
|
||||
private final class SessionIdHeaderPostProcessor implements MessagePostProcessor {
|
||||
|
||||
private final Message<?> inputMessage;
|
||||
|
||||
|
||||
public SessionIdHeaderPostProcessor(Message<?> inputMessage) {
|
||||
this.inputMessage = inputMessage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> postProcessMessage(Message<?> message) {
|
||||
String headerName = SimpMessageHeaderAccessor.SESSION_ID;
|
||||
String sessionId = (String) this.inputMessage.getHeaders().get(headerName);
|
||||
return MessageBuilder.fromMessage(message).setHeader(headerName, sessionId).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.simp.annotation.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.core.MessagePostProcessor;
|
||||
import org.springframework.messaging.core.MessageSendingOperations;
|
||||
import org.springframework.messaging.handler.annotation.ReplyTo;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.annotation.ReplyToUser;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeEvent;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A {@link HandlerMethodReturnValueHandler} for replying directly to a subscription. It
|
||||
* supports methods annotated with {@link SubscribeEvent} that do not also annotated with
|
||||
* neither {@link ReplyTo} nor {@link ReplyToUser}.
|
||||
*
|
||||
* <p>The value returned from the method is converted, and turned to a {@link Message} and
|
||||
* then enriched with the sessionId, subscriptionId, and destination of the input message.
|
||||
* The message is then sent directly back to the connected client.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SubscriptionMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
|
||||
|
||||
private final MessageSendingOperations<String> messagingTemplate;
|
||||
|
||||
|
||||
public SubscriptionMethodReturnValueHandler(MessageSendingOperations<String> messagingTemplate) {
|
||||
Assert.notNull(messagingTemplate, "messagingTemplate is required");
|
||||
this.messagingTemplate = messagingTemplate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
return ((returnType.getMethodAnnotation(SubscribeEvent.class) != null)
|
||||
&& (returnType.getMethodAnnotation(ReplyTo.class) == null)
|
||||
&& (returnType.getMethodAnnotation(ReplyToUser.class) == null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleReturnValue(Object returnValue, MethodParameter returnType, Message<?> message)
|
||||
throws Exception {
|
||||
|
||||
if (returnValue == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SimpMessageHeaderAccessor inputHeaders = SimpMessageHeaderAccessor.wrap(message);
|
||||
String destination = inputHeaders.getDestination();
|
||||
|
||||
Assert.state(inputHeaders.getSubscriptionId() != null,
|
||||
"No subsriptiondId in input message. Add @ReplyTo or @ReplyToUser to method: "
|
||||
+ returnType.getMethod());
|
||||
|
||||
MessagePostProcessor postProcessor = new InputHeaderCopyingPostProcessor(inputHeaders);
|
||||
this.messagingTemplate.convertAndSend(destination, returnValue, postProcessor);
|
||||
}
|
||||
|
||||
|
||||
private final class InputHeaderCopyingPostProcessor implements MessagePostProcessor {
|
||||
|
||||
private final SimpMessageHeaderAccessor inputHeaders;
|
||||
|
||||
|
||||
public InputHeaderCopyingPostProcessor(SimpMessageHeaderAccessor inputHeaders) {
|
||||
this.inputHeaders = inputHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> postProcessMessage(Message<?> message) {
|
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
|
||||
return MessageBuilder.fromMessage(message)
|
||||
.setHeader(SimpMessageHeaderAccessor.SESSION_ID, this.inputHeaders.getSessionId())
|
||||
.setHeader(SimpMessageHeaderAccessor.SUBSCRIPTION_ID, this.inputHeaders.getSubscriptionId())
|
||||
.copyHeaders(headers.toMap()).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,24 +38,26 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.handler.annotation.support.MessageBodyArgumentResolver;
|
||||
import org.springframework.messaging.handler.annotation.support.MessageExceptionHandlerMethodResolver;
|
||||
import org.springframework.messaging.handler.method.InvocableMessageHandlerMethod;
|
||||
import org.springframework.messaging.handler.method.MessageArgumentResolverComposite;
|
||||
import org.springframework.messaging.handler.method.MessageReturnValueHandlerComposite;
|
||||
import org.springframework.messaging.handler.annotation.support.ExceptionHandlerMethodResolver;
|
||||
import org.springframework.messaging.handler.annotation.support.MessageBodyMethodArgumentResolver;
|
||||
import org.springframework.messaging.handler.method.HandlerMethod;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodArgumentResolverComposite;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodReturnValueHandlerComposite;
|
||||
import org.springframework.messaging.handler.method.HandlerMethodSelector;
|
||||
import org.springframework.messaging.handler.method.InvocableHandlerMethod;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeEvent;
|
||||
import org.springframework.messaging.simp.annotation.UnsubscribeEvent;
|
||||
import org.springframework.messaging.simp.annotation.support.DefaultMessageReturnValueHandler;
|
||||
import org.springframework.messaging.simp.annotation.support.PrincipalMessageArgumentResolver;
|
||||
import org.springframework.messaging.simp.annotation.support.PrincipalMethodArgumentResolver;
|
||||
import org.springframework.messaging.simp.annotation.support.ReplyToMethodReturnValueHandler;
|
||||
import org.springframework.messaging.simp.annotation.support.SubscriptionMethodReturnValueHandler;
|
||||
import org.springframework.messaging.support.converter.MessageConverter;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.HandlerMethodSelector;
|
||||
|
||||
|
||||
/**
|
||||
@@ -80,12 +82,12 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
|
||||
|
||||
private Map<MappingInfo, HandlerMethod> unsubscribeMethods = new HashMap<MappingInfo, HandlerMethod>();
|
||||
|
||||
private final Map<Class<?>, MessageExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, MessageExceptionHandlerMethodResolver>(64);
|
||||
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||
new ConcurrentHashMap<Class<?>, ExceptionHandlerMethodResolver>(64);
|
||||
|
||||
private MessageArgumentResolverComposite argumentResolvers = new MessageArgumentResolverComposite();
|
||||
private HandlerMethodArgumentResolverComposite argumentResolvers = new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
private MessageReturnValueHandlerComposite returnValueHandlers = new MessageReturnValueHandlerComposite();
|
||||
private HandlerMethodReturnValueHandlerComposite returnValueHandlers = new HandlerMethodReturnValueHandlerComposite();
|
||||
|
||||
|
||||
/**
|
||||
@@ -116,11 +118,17 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
|
||||
|
||||
initHandlerMethods();
|
||||
|
||||
this.argumentResolvers.addResolver(new PrincipalMessageArgumentResolver());
|
||||
this.argumentResolvers.addResolver(new MessageBodyArgumentResolver(this.messageConverter));
|
||||
this.argumentResolvers.addResolver(new PrincipalMethodArgumentResolver());
|
||||
this.argumentResolvers.addResolver(new MessageBodyMethodArgumentResolver(this.messageConverter));
|
||||
|
||||
this.returnValueHandlers.addHandler(new DefaultMessageReturnValueHandler(
|
||||
this.inboundChannel, this.outboundChannel, this.messageConverter));
|
||||
SimpMessagingTemplate inboundMessagingTemplate = new SimpMessagingTemplate(this.inboundChannel);
|
||||
inboundMessagingTemplate.setConverter(this.messageConverter);
|
||||
|
||||
SimpMessagingTemplate outboundMessagingTemplate = new SimpMessagingTemplate(this.outboundChannel);
|
||||
outboundMessagingTemplate.setConverter(this.messageConverter);
|
||||
|
||||
this.returnValueHandlers.addHandler(new ReplyToMethodReturnValueHandler(inboundMessagingTemplate));
|
||||
this.returnValueHandlers.addHandler(new SubscriptionMethodReturnValueHandler(outboundMessagingTemplate));
|
||||
}
|
||||
|
||||
protected void initHandlerMethods() {
|
||||
@@ -213,8 +221,7 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
|
||||
|
||||
HandlerMethod handlerMethod = match.createWithResolvedBean();
|
||||
|
||||
// TODO: avoid re-creating invocableHandlerMethod
|
||||
InvocableMessageHandlerMethod invocableHandlerMethod = new InvocableMessageHandlerMethod(handlerMethod);
|
||||
InvocableHandlerMethod invocableHandlerMethod = new InvocableHandlerMethod(handlerMethod);
|
||||
invocableHandlerMethod.setMessageMethodArgumentResolvers(this.argumentResolvers);
|
||||
|
||||
try {
|
||||
@@ -237,11 +244,11 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
|
||||
|
||||
private void invokeExceptionHandler(Message<?> message, HandlerMethod handlerMethod, Exception ex) {
|
||||
|
||||
InvocableMessageHandlerMethod exceptionHandlerMethod;
|
||||
InvocableHandlerMethod exceptionHandlerMethod;
|
||||
Class<?> beanType = handlerMethod.getBeanType();
|
||||
MessageExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
|
||||
ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(beanType);
|
||||
if (resolver == null) {
|
||||
resolver = new MessageExceptionHandlerMethodResolver(beanType);
|
||||
resolver = new ExceptionHandlerMethodResolver(beanType);
|
||||
this.exceptionHandlerCache.put(beanType, resolver);
|
||||
}
|
||||
|
||||
@@ -251,7 +258,7 @@ public class AnnotationMethodMessageHandler implements MessageHandler, Applicati
|
||||
return;
|
||||
}
|
||||
|
||||
exceptionHandlerMethod = new InvocableMessageHandlerMethod(handlerMethod.getBean(), method);
|
||||
exceptionHandlerMethod = new InvocableHandlerMethod(handlerMethod.getBean(), method);
|
||||
exceptionHandlerMethod.setMessageMethodArgumentResolvers(this.argumentResolvers);
|
||||
|
||||
try {
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.springframework.util.StringUtils;
|
||||
* Supports destinations prefixed with "/user/{username}" and resolves them into a
|
||||
* destination to which the user is currently subscribed by appending the user session id.
|
||||
* For example a destination such as "/user/john/queue/trade-confirmation" would resolve
|
||||
* to "/trade-confirmation/i9oqdfzo" if "i9oqdfzo" is the user's session id.
|
||||
* to "/queue/trade-confirmation/i9oqdfzo" if "i9oqdfzo" is the user's session id.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
|
||||
@@ -18,8 +18,8 @@ package org.springframework.messaging.support.channel;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.core.task.TaskExecutor;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
@@ -31,28 +31,28 @@ import org.springframework.messaging.SubscribableChannel;
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class TaskExecutorSubscribableChannel extends AbstractSubscribableChannel {
|
||||
public class ExecutorSubscribableChannel extends AbstractSubscribableChannel {
|
||||
|
||||
private final TaskExecutor executor;
|
||||
private final Executor executor;
|
||||
|
||||
private final Set<MessageHandler> handlers = new CopyOnWriteArraySet<MessageHandler>();
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@link TaskExecutorSubscribableChannel} instance where messages will be sent
|
||||
* Create a new {@link ExecutorSubscribableChannel} instance where messages will be sent
|
||||
* in the callers thread.
|
||||
*/
|
||||
public TaskExecutorSubscribableChannel() {
|
||||
public ExecutorSubscribableChannel() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link TaskExecutorSubscribableChannel} instance where messages will be sent
|
||||
* Create a new {@link ExecutorSubscribableChannel} instance where messages will be sent
|
||||
* via the specified executor.
|
||||
* @param executor the executor used to send the message or {@code null} to execute in
|
||||
* the callers thread.
|
||||
*/
|
||||
public TaskExecutorSubscribableChannel(TaskExecutor executor) {
|
||||
public ExecutorSubscribableChannel(Executor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user