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}