001/*
002 * Copyright 2002-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jms.listener.adapter;
018
019import javax.jms.JMSException;
020import javax.jms.Session;
021
022import org.springframework.core.MethodParameter;
023import org.springframework.jms.support.JmsHeaderMapper;
024import org.springframework.jms.support.converter.MessageConversionException;
025import org.springframework.lang.Nullable;
026import org.springframework.messaging.Message;
027import org.springframework.messaging.MessagingException;
028import org.springframework.messaging.core.AbstractMessageSendingTemplate;
029import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
030import org.springframework.messaging.support.MessageBuilder;
031import org.springframework.util.Assert;
032
033/**
034 * A {@link javax.jms.MessageListener} adapter that invokes a configurable
035 * {@link InvocableHandlerMethod}.
036 *
037 * <p>Wraps the incoming {@link javax.jms.Message} to Spring's {@link Message}
038 * abstraction, copying the JMS standard headers using a configurable
039 * {@link JmsHeaderMapper}.
040 *
041 * <p>The original {@link javax.jms.Message} and the {@link javax.jms.Session}
042 * are provided as additional arguments so that these can be injected as
043 * method arguments if necessary.
044 *
045 * @author Stephane Nicoll
046 * @since 4.1
047 * @see Message
048 * @see JmsHeaderMapper
049 * @see InvocableHandlerMethod
050 */
051public class MessagingMessageListenerAdapter extends AbstractAdaptableMessageListener {
052
053        @Nullable
054        private InvocableHandlerMethod handlerMethod;
055
056
057        /**
058         * Set the {@link InvocableHandlerMethod} to use to invoke the method
059         * processing an incoming {@link javax.jms.Message}.
060         */
061        public void setHandlerMethod(InvocableHandlerMethod handlerMethod) {
062                this.handlerMethod = handlerMethod;
063        }
064
065        private InvocableHandlerMethod getHandlerMethod() {
066                Assert.state(this.handlerMethod != null, "No HandlerMethod set");
067                return this.handlerMethod;
068        }
069
070
071        @Override
072        public void onMessage(javax.jms.Message jmsMessage, @Nullable Session session) throws JMSException {
073                Message<?> message = toMessagingMessage(jmsMessage);
074                if (logger.isDebugEnabled()) {
075                        logger.debug("Processing [" + message + "]");
076                }
077                Object result = invokeHandler(jmsMessage, session, message);
078                if (result != null) {
079                        handleResult(result, jmsMessage, session);
080                }
081                else {
082                        logger.trace("No result object given - no result to handle");
083                }
084        }
085
086        @Override
087        protected Object preProcessResponse(Object result) {
088                MethodParameter returnType = getHandlerMethod().getReturnType();
089                if (result instanceof Message) {
090                        return MessageBuilder.fromMessage((Message<?>) result)
091                                        .setHeader(AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
092                }
093                return MessageBuilder.withPayload(result).setHeader(
094                                AbstractMessageSendingTemplate.CONVERSION_HINT_HEADER, returnType).build();
095        }
096
097        protected Message<?> toMessagingMessage(javax.jms.Message jmsMessage) {
098                try {
099                        return (Message<?>) getMessagingMessageConverter().fromMessage(jmsMessage);
100                }
101                catch (JMSException ex) {
102                        throw new MessageConversionException("Could not convert JMS message", ex);
103                }
104        }
105
106        /**
107         * Invoke the handler, wrapping any exception to a {@link ListenerExecutionFailedException}
108         * with a dedicated error message.
109         */
110        @Nullable
111        private Object invokeHandler(javax.jms.Message jmsMessage, @Nullable Session session, Message<?> message) {
112                InvocableHandlerMethod handlerMethod = getHandlerMethod();
113                try {
114                        return handlerMethod.invoke(message, jmsMessage, session);
115                }
116                catch (MessagingException ex) {
117                        throw new ListenerExecutionFailedException(
118                                        createMessagingErrorMessage("Listener method could not be invoked with incoming message"), ex);
119                }
120                catch (Exception ex) {
121                        throw new ListenerExecutionFailedException("Listener method '" +
122                                        handlerMethod.getMethod().toGenericString() + "' threw exception", ex);
123                }
124        }
125
126        private String createMessagingErrorMessage(String description) {
127                InvocableHandlerMethod handlerMethod = getHandlerMethod();
128                StringBuilder sb = new StringBuilder(description).append("\n")
129                                .append("Endpoint handler details:\n")
130                                .append("Method [").append(handlerMethod.getMethod()).append("]\n")
131                                .append("Bean [").append(handlerMethod.getBean()).append("]\n");
132                return sb.toString();
133        }
134
135}