io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter Maven / Gradle / Ivy
/*
* Copyright 2002-2017 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 io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.adapter;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageProperties;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.AmqpHeaderMapper;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.MessageConversionException;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.MessageConverter;
import io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.converter.MessagingMessageConverter;
import io.bitsensor.plugins.shaded.org.springframework.core.MethodParameter;
import io.bitsensor.plugins.shaded.org.springframework.messaging.Message;
import io.bitsensor.plugins.shaded.org.springframework.messaging.MessagingException;
import io.bitsensor.plugins.shaded.org.springframework.messaging.handler.annotation.Payload;
import io.bitsensor.plugins.shaded.org.springframework.util.Assert;
import io.bitsensor.plugins.shaded.com.rabbitmq.client.Channel;
/**
* A {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.MessageListener MessageListener}
* adapter that invokes a configurable {@link HandlerAdapter}.
*
* Wraps the incoming {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message
* AMQP Message} to Spring's {@link Message} abstraction, copying the
* standard headers using a configurable
* {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.AmqpHeaderMapper AmqpHeaderMapper}.
*
*
The original {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message Message} and
* the {@link Channel} are provided as additional arguments so that these can
* be injected as method arguments if necessary.
*
* @author Stephane Nicoll
* @author Gary Russell
* @author Artem Bilan
*
* @since 1.4
*/
public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
private HandlerAdapter handlerMethod;
private final MessagingMessageConverterAdapter messagingMessageConverter;
public MessagingMessageListenerAdapter() {
this(null, null);
}
public MessagingMessageListenerAdapter(Object bean, Method method) {
this.messagingMessageConverter = new MessagingMessageConverterAdapter(bean, method);
}
/**
* Set the {@link HandlerAdapter} to use to invoke the method
* processing an incoming {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message}.
* @param handlerMethod {@link HandlerAdapter} instance.
*/
public void setHandlerMethod(HandlerAdapter handlerMethod) {
this.handlerMethod = handlerMethod;
}
/**
* Set the {@link AmqpHeaderMapper} implementation to use to map the standard
* AMQP headers. By default, a {@link io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.SimpleAmqpHeaderMapper
* SimpleAmqpHeaderMapper} is used.
* @param headerMapper the {@link AmqpHeaderMapper} instance.
* @see io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.support.SimpleAmqpHeaderMapper
*/
public void setHeaderMapper(AmqpHeaderMapper headerMapper) {
Assert.notNull(headerMapper, "HeaderMapper must not be null");
this.messagingMessageConverter.setHeaderMapper(headerMapper);
}
/**
* @return the {@link MessagingMessageConverter} for this listener,
* being able to convert {@link io.bitsensor.plugins.shaded.org.springframework.messaging.Message}.
*/
protected final MessagingMessageConverter getMessagingMessageConverter() {
return this.messagingMessageConverter;
}
@Override
public void onMessage(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message amqpMessage, Channel channel) throws Exception {
Message message = toMessagingMessage(amqpMessage);
if (logger.isDebugEnabled()) {
logger.debug("Processing [" + message + "]");
}
Object result = invokeHandler(amqpMessage, channel, message);
if (result != null) {
handleResult(result, amqpMessage, channel, message);
}
else {
logger.trace("No result object given - no result to handle");
}
}
protected Message toMessagingMessage(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message amqpMessage) {
return (Message) getMessagingMessageConverter().fromMessage(amqpMessage);
}
/**
* Invoke the handler, wrapping any exception to a {@link ListenerExecutionFailedException}
* with a dedicated error message.
*/
private Object invokeHandler(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message amqpMessage, Channel channel,
Message message) {
try {
return this.handlerMethod.invoke(message, amqpMessage, channel);
}
catch (io.bitsensor.plugins.shaded.org.springframework.messaging.converter.MessageConversionException ex) {
throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
"be invoked with the incoming message", message.getPayload()),
new MessageConversionException("Cannot handle message", ex),
amqpMessage);
}
catch (MessagingException ex) {
throw new ListenerExecutionFailedException(createMessagingErrorMessage("Listener method could not " +
"be invoked with the incoming message", message.getPayload()), ex, amqpMessage);
}
catch (Exception ex) {
throw new ListenerExecutionFailedException("Listener method '" +
this.handlerMethod.getMethodAsString(message.getPayload()) + "' threw exception", ex, amqpMessage);
}
}
private String createMessagingErrorMessage(String description, Object payload) {
return description + "\n"
+ "Endpoint handler details:\n"
+ "Method [" + this.handlerMethod.getMethodAsString(payload) + "]\n"
+ "Bean [" + this.handlerMethod.getBean() + "]";
}
/**
* Build a Rabbit message to be sent as response based on the given result object.
* @param channel the Rabbit Channel to operate on
* @param result the content of the message, as returned from the listener method
* @return the Rabbit Message
(never null
)
* @throws Exception if thrown by Rabbit API methods
* @see #setMessageConverter
*/
@Override
protected io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message buildMessage(Channel channel, Object result) throws Exception {
MessageConverter converter = getMessageConverter();
if (converter != null && !(result instanceof io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message)) {
if (result instanceof io.bitsensor.plugins.shaded.org.springframework.messaging.Message) {
return this.messagingMessageConverter.toMessage(result, new MessageProperties());
}
else {
return converter.toMessage(result, new MessageProperties());
}
}
else {
if (!(result instanceof io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message)) {
throw new MessageConversionException("No MessageConverter specified - cannot handle message ["
+ result + "]");
}
return (io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message) result;
}
}
/**
* Delegates payload extraction to
* {@link #extractMessage(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message message)}
* to enforce backward compatibility. Uses this listener adapter's converter instead of
* the one configured in the converter adapter.
* If the inbound message has no type information and the configured message converter
* supports it, we attempt to infer the conversion type from the method signature.
*/
private final class MessagingMessageConverterAdapter extends MessagingMessageConverter {
private final Object bean;
private final Method method;
private final Type inferredArgumentType;
private MessagingMessageConverterAdapter(Object bean, Method method) {
this.bean = bean;
this.method = method;
this.inferredArgumentType = determineInferredType();
if (logger.isDebugEnabled() && this.inferredArgumentType != null) {
logger.debug("Inferred argument type for " + method.toString() + " is " + this.inferredArgumentType);
}
}
@Override
protected Object extractPayload(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message message) {
MessageProperties messageProperties = message.getMessageProperties();
if (this.bean != null) {
messageProperties.setTargetBean(this.bean);
}
if (this.method != null) {
messageProperties.setTargetMethod(this.method);
if (this.inferredArgumentType != null) {
messageProperties.setInferredArgumentType(this.inferredArgumentType);
}
}
return extractMessage(message);
}
private Type determineInferredType() {
if (this.method == null) {
return null;
}
Type genericParameterType = null;
for (int i = 0; i < this.method.getParameterTypes().length; i++) {
MethodParameter methodParameter = new MethodParameter(this.method, i);
/*
* We're looking for a single non-annotated parameter, or one annotated with @Payload.
* We ignore parameters with type Message because they are not involved with conversion.
*/
if (isEligibleParameter(methodParameter)
&& (methodParameter.getParameterAnnotations().length == 0
|| methodParameter.hasParameterAnnotation(Payload.class))) {
if (genericParameterType == null) {
genericParameterType = methodParameter.getGenericParameterType();
if (genericParameterType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericParameterType;
if (parameterizedType.getRawType().equals(Message.class)) {
genericParameterType = ((ParameterizedType) genericParameterType)
.getActualTypeArguments()[0];
}
}
}
else {
if (MessagingMessageListenerAdapter.this.logger.isDebugEnabled()) {
MessagingMessageListenerAdapter.this.logger
.debug("Ambiguous parameters for target payload for method " + this.method
+ "; no inferred type header added");
}
return null;
}
}
}
return genericParameterType;
}
/*
* Don't consider parameter types that are available after conversion.
* Message, Message and Channel.
*/
private boolean isEligibleParameter(MethodParameter methodParameter) {
Type parameterType = methodParameter.getGenericParameterType();
if (parameterType.equals(Channel.class)
|| parameterType.equals(io.bitsensor.plugins.shaded.io.bitsensor.plugins.shaded.org.springframework.amqp.core.Message.class)) {
return false;
}
if (parameterType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) parameterType;
if (parameterizedType.getRawType().equals(Message.class)) {
return !(parameterizedType.getActualTypeArguments()[0] instanceof WildcardType);
}
}
return !parameterType.equals(Message.class); // could be Message without a generic type
}
}
}