001/*
002 * Copyright 2002-2018 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.remoting;
018
019import javax.jms.JMSException;
020import javax.jms.Message;
021import javax.jms.MessageFormatException;
022import javax.jms.MessageProducer;
023import javax.jms.Session;
024
025import org.springframework.beans.factory.InitializingBean;
026import org.springframework.jms.listener.SessionAwareMessageListener;
027import org.springframework.jms.support.JmsUtils;
028import org.springframework.jms.support.converter.MessageConverter;
029import org.springframework.jms.support.converter.SimpleMessageConverter;
030import org.springframework.lang.Nullable;
031import org.springframework.remoting.support.RemoteInvocation;
032import org.springframework.remoting.support.RemoteInvocationBasedExporter;
033import org.springframework.remoting.support.RemoteInvocationResult;
034
035/**
036 * JMS message listener that exports the specified service bean as a
037 * JMS service endpoint, accessible via a JMS invoker proxy.
038 *
039 * <p>Note that this class implements Spring's
040 * {@link org.springframework.jms.listener.SessionAwareMessageListener}
041 * interface, since it requires access to the active JMS Session.
042 * Hence, this class can only be used with message listener containers
043 * which support the SessionAwareMessageListener interface (e.g. Spring's
044 * {@link org.springframework.jms.listener.DefaultMessageListenerContainer}).
045 *
046 * <p>Thanks to James Strachan for the original prototype that this
047 * JMS invoker mechanism was inspired by!
048 *
049 * @author Juergen Hoeller
050 * @author James Strachan
051 * @since 2.0
052 * @see JmsInvokerClientInterceptor
053 * @see JmsInvokerProxyFactoryBean
054 */
055public class JmsInvokerServiceExporter extends RemoteInvocationBasedExporter
056                implements SessionAwareMessageListener<Message>, InitializingBean {
057
058        private MessageConverter messageConverter = new SimpleMessageConverter();
059
060        private boolean ignoreInvalidRequests = true;
061
062        @Nullable
063        private Object proxy;
064
065
066        /**
067         * Specify the MessageConverter to use for turning request messages into
068         * {@link org.springframework.remoting.support.RemoteInvocation} objects,
069         * as well as {@link org.springframework.remoting.support.RemoteInvocationResult}
070         * objects into response messages.
071         * <p>Default is a {@link org.springframework.jms.support.converter.SimpleMessageConverter},
072         * using a standard JMS {@link javax.jms.ObjectMessage} for each invocation /
073         * invocation result object.
074         * <p>Custom implementations may generally adapt Serializables into
075         * special kinds of messages, or might be specifically tailored for
076         * translating RemoteInvocation(Result)s into specific kinds of messages.
077         */
078        public void setMessageConverter(@Nullable MessageConverter messageConverter) {
079                this.messageConverter = (messageConverter != null ? messageConverter : new SimpleMessageConverter());
080        }
081
082        /**
083         * Set whether invalidly formatted messages should be discarded.
084         * Default is "true".
085         * <p>Switch this flag to "false" to throw an exception back to the
086         * listener container. This will typically lead to redelivery of
087         * the message, which is usually undesirable - since the message
088         * content will be the same (that is, still invalid).
089         */
090        public void setIgnoreInvalidRequests(boolean ignoreInvalidRequests) {
091                this.ignoreInvalidRequests = ignoreInvalidRequests;
092        }
093
094        @Override
095        public void afterPropertiesSet() {
096                this.proxy = getProxyForService();
097        }
098
099
100        @Override
101        public void onMessage(Message requestMessage, Session session) throws JMSException {
102                RemoteInvocation invocation = readRemoteInvocation(requestMessage);
103                if (invocation != null) {
104                        RemoteInvocationResult result = invokeAndCreateResult(invocation, this.proxy);
105                        writeRemoteInvocationResult(requestMessage, session, result);
106                }
107        }
108
109        /**
110         * Read a RemoteInvocation from the given JMS message.
111         * @param requestMessage current request message
112         * @return the RemoteInvocation object (or {@code null}
113         * in case of an invalid message that will simply be ignored)
114         * @throws javax.jms.JMSException in case of message access failure
115         */
116        @Nullable
117        protected RemoteInvocation readRemoteInvocation(Message requestMessage) throws JMSException {
118                Object content = this.messageConverter.fromMessage(requestMessage);
119                if (content instanceof RemoteInvocation) {
120                        return (RemoteInvocation) content;
121                }
122                return onInvalidRequest(requestMessage);
123        }
124
125
126        /**
127         * Send the given RemoteInvocationResult as a JMS message to the originator.
128         * @param requestMessage current request message
129         * @param session the JMS Session to use
130         * @param result the RemoteInvocationResult object
131         * @throws javax.jms.JMSException if thrown by trying to send the message
132         */
133        protected void writeRemoteInvocationResult(
134                        Message requestMessage, Session session, RemoteInvocationResult result) throws JMSException {
135
136                Message response = createResponseMessage(requestMessage, session, result);
137                MessageProducer producer = session.createProducer(requestMessage.getJMSReplyTo());
138                try {
139                        producer.send(response);
140                }
141                finally {
142                        JmsUtils.closeMessageProducer(producer);
143                }
144        }
145
146        /**
147         * Create the invocation result response message.
148         * <p>The default implementation creates a JMS ObjectMessage for the given
149         * RemoteInvocationResult object. It sets the response's correlation id
150         * to the request message's correlation id, if any; otherwise to the
151         * request message id.
152         * @param request the original request message
153         * @param session the JMS session to use
154         * @param result the invocation result
155         * @return the message response to send
156         * @throws javax.jms.JMSException if creating the message failed
157         */
158        protected Message createResponseMessage(Message request, Session session, RemoteInvocationResult result)
159                        throws JMSException {
160
161                Message response = this.messageConverter.toMessage(result, session);
162                String correlation = request.getJMSCorrelationID();
163                if (correlation == null) {
164                        correlation = request.getJMSMessageID();
165                }
166                response.setJMSCorrelationID(correlation);
167                return response;
168        }
169
170        /**
171         * Callback that is invoked by {@link #readRemoteInvocation}
172         * when it encounters an invalid request message.
173         * <p>The default implementation either discards the invalid message or
174         * throws a MessageFormatException - according to the "ignoreInvalidRequests"
175         * flag, which is set to "true" (that is, discard invalid messages) by default.
176         * @param requestMessage the invalid request message
177         * @return the RemoteInvocation to expose for the invalid request (typically
178         * {@code null} in case of an invalid message that will simply be ignored)
179         * @throws javax.jms.JMSException in case of the invalid request supposed
180         * to lead to an exception (instead of ignoring it)
181         * @see #readRemoteInvocation
182         * @see #setIgnoreInvalidRequests
183         */
184        @Nullable
185        protected RemoteInvocation onInvalidRequest(Message requestMessage) throws JMSException {
186                if (this.ignoreInvalidRequests) {
187                        if (logger.isDebugEnabled()) {
188                                logger.debug("Invalid request message will be discarded: " + requestMessage);
189                        }
190                        return null;
191                }
192                else {
193                        throw new MessageFormatException("Invalid request message: " + requestMessage);
194                }
195        }
196
197}