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