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}