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.listener.adapter; 018 019import java.lang.reflect.InvocationTargetException; 020 021import javax.jms.JMSException; 022import javax.jms.Message; 023import javax.jms.MessageListener; 024import javax.jms.Session; 025 026import org.springframework.jms.listener.SessionAwareMessageListener; 027import org.springframework.jms.listener.SubscriptionNameProvider; 028import org.springframework.jms.support.converter.MessageConverter; 029import org.springframework.jms.support.converter.SimpleMessageConverter; 030import org.springframework.lang.Nullable; 031import org.springframework.util.Assert; 032import org.springframework.util.MethodInvoker; 033import org.springframework.util.ObjectUtils; 034 035/** 036 * Message listener adapter that delegates the handling of messages to target 037 * listener methods via reflection, with flexible message type conversion. 038 * Allows listener methods to operate on message content types, completely 039 * independent from the JMS API. 040 * 041 * <p>By default, the content of incoming JMS messages gets extracted before 042 * being passed into the target listener method, to let the target method 043 * operate on message content types such as String or byte array instead of 044 * the raw {@link Message}. Message type conversion is delegated to a Spring 045 * JMS {@link MessageConverter}. By default, a {@link SimpleMessageConverter} 046 * will be used. (If you do not want such automatic message conversion taking 047 * place, then be sure to set the {@link #setMessageConverter MessageConverter} 048 * to {@code null}.) 049 * 050 * <p>If a target listener method returns a non-null object (typically of a 051 * message content type such as {@code String} or byte array), it will get 052 * wrapped in a JMS {@code Message} and sent to the response destination 053 * (either the JMS "reply-to" destination or a 054 * {@link #setDefaultResponseDestination(javax.jms.Destination) specified default 055 * destination}). 056 * 057 * <p><b>Note:</b> The sending of response messages is only available when 058 * using the {@link SessionAwareMessageListener} entry point (typically through a 059 * Spring message listener container). Usage as standard JMS {@link MessageListener} 060 * does <i>not</i> support the generation of response messages. 061 * 062 * <p>Find below some examples of method signatures compliant with this 063 * adapter class. This first example handles all {@code Message} types 064 * and gets passed the contents of each {@code Message} type as an 065 * argument. No {@code Message} will be sent back as all of these 066 * methods return {@code void}. 067 * 068 * <pre class="code">public interface MessageContentsDelegate { 069 * void handleMessage(String text); 070 * void handleMessage(Map map); 071 * void handleMessage(byte[] bytes); 072 * void handleMessage(Serializable obj); 073 * }</pre> 074 * 075 * This next example handles all {@code Message} types and gets 076 * passed the actual (raw) {@code Message} as an argument. Again, no 077 * {@code Message} will be sent back as all of these methods return 078 * {@code void}. 079 * 080 * <pre class="code">public interface RawMessageDelegate { 081 * void handleMessage(TextMessage message); 082 * void handleMessage(MapMessage message); 083 * void handleMessage(BytesMessage message); 084 * void handleMessage(ObjectMessage message); 085 * }</pre> 086 * 087 * This next example illustrates a {@code Message} delegate 088 * that just consumes the {@code String} contents of 089 * {@link javax.jms.TextMessage TextMessages}. Notice also how the 090 * name of the {@code Message} handling method is different from the 091 * {@link #ORIGINAL_DEFAULT_LISTENER_METHOD original} (this will have to 092 * be configured in the attendant bean definition). Again, no {@code Message} 093 * will be sent back as the method returns {@code void}. 094 * 095 * <pre class="code">public interface TextMessageContentDelegate { 096 * void onMessage(String text); 097 * }</pre> 098 * 099 * This final example illustrates a {@code Message} delegate 100 * that just consumes the {@code String} contents of 101 * {@link javax.jms.TextMessage TextMessages}. Notice how the return type 102 * of this method is {@code String}: This will result in the configured 103 * {@link MessageListenerAdapter} sending a {@link javax.jms.TextMessage} in response. 104 * 105 * <pre class="code">public interface ResponsiveTextMessageContentDelegate { 106 * String handleMessage(String text); 107 * }</pre> 108 * 109 * For further examples and discussion please do refer to the Spring 110 * reference documentation which describes this class (and it's attendant 111 * XML configuration) in detail. 112 * 113 * @author Juergen Hoeller 114 * @since 2.0 115 * @see #setDelegate 116 * @see #setDefaultListenerMethod 117 * @see #setDefaultResponseDestination 118 * @see #setMessageConverter 119 * @see org.springframework.jms.support.converter.SimpleMessageConverter 120 * @see org.springframework.jms.listener.SessionAwareMessageListener 121 * @see org.springframework.jms.listener.AbstractMessageListenerContainer#setMessageListener 122 */ 123public class MessageListenerAdapter extends AbstractAdaptableMessageListener implements SubscriptionNameProvider { 124 125 /** 126 * Out-of-the-box value for the default listener method: "handleMessage". 127 */ 128 public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = "handleMessage"; 129 130 131 private Object delegate; 132 133 private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD; 134 135 136 /** 137 * Create a new {@link MessageListenerAdapter} with default settings. 138 */ 139 public MessageListenerAdapter() { 140 this.delegate = this; 141 } 142 143 /** 144 * Create a new {@link MessageListenerAdapter} for the given delegate. 145 * @param delegate the delegate object 146 */ 147 public MessageListenerAdapter(Object delegate) { 148 Assert.notNull(delegate, "Delegate must not be null"); 149 this.delegate = delegate; 150 } 151 152 153 /** 154 * Set a target object to delegate message listening to. 155 * Specified listener methods have to be present on this target object. 156 * <p>If no explicit delegate object has been specified, listener 157 * methods are expected to present on this adapter instance, that is, 158 * on a custom subclass of this adapter, defining listener methods. 159 */ 160 public void setDelegate(Object delegate) { 161 Assert.notNull(delegate, "Delegate must not be null"); 162 this.delegate = delegate; 163 } 164 165 /** 166 * Return the target object to delegate message listening to. 167 */ 168 protected Object getDelegate() { 169 return this.delegate; 170 } 171 172 /** 173 * Specify the name of the default listener method to delegate to, 174 * for the case where no specific listener method has been determined. 175 * Out-of-the-box value is {@link #ORIGINAL_DEFAULT_LISTENER_METHOD "handleMessage"}. 176 * @see #getListenerMethodName 177 */ 178 public void setDefaultListenerMethod(String defaultListenerMethod) { 179 this.defaultListenerMethod = defaultListenerMethod; 180 } 181 182 /** 183 * Return the name of the default listener method to delegate to. 184 */ 185 protected String getDefaultListenerMethod() { 186 return this.defaultListenerMethod; 187 } 188 189 190 /** 191 * Spring {@link SessionAwareMessageListener} entry point. 192 * <p>Delegates the message to the target listener method, with appropriate 193 * conversion of the message argument. If the target method returns a 194 * non-null object, wrap in a JMS message and send it back. 195 * @param message the incoming JMS message 196 * @param session the JMS session to operate on 197 * @throws JMSException if thrown by JMS API methods 198 */ 199 @Override 200 @SuppressWarnings("unchecked") 201 public void onMessage(Message message, @Nullable Session session) throws JMSException { 202 // Check whether the delegate is a MessageListener impl itself. 203 // In that case, the adapter will simply act as a pass-through. 204 Object delegate = getDelegate(); 205 if (delegate != this) { 206 if (delegate instanceof SessionAwareMessageListener) { 207 Assert.state(session != null, "Session is required for SessionAwareMessageListener"); 208 ((SessionAwareMessageListener<Message>) delegate).onMessage(message, session); 209 return; 210 } 211 if (delegate instanceof MessageListener) { 212 ((MessageListener) delegate).onMessage(message); 213 return; 214 } 215 } 216 217 // Regular case: find a handler method reflectively. 218 Object convertedMessage = extractMessage(message); 219 String methodName = getListenerMethodName(message, convertedMessage); 220 221 // Invoke the handler method with appropriate arguments. 222 Object[] listenerArguments = buildListenerArguments(convertedMessage); 223 Object result = invokeListenerMethod(methodName, listenerArguments); 224 if (result != null) { 225 handleResult(result, message, session); 226 } 227 else { 228 logger.trace("No result object given - no result to handle"); 229 } 230 } 231 232 @Override 233 public String getSubscriptionName() { 234 Object delegate = getDelegate(); 235 if (delegate != this && delegate instanceof SubscriptionNameProvider) { 236 return ((SubscriptionNameProvider) delegate).getSubscriptionName(); 237 } 238 else { 239 return delegate.getClass().getName(); 240 } 241 } 242 243 /** 244 * Determine the name of the listener method that is supposed to 245 * handle the given message. 246 * <p>The default implementation simply returns the configured 247 * default listener method, if any. 248 * @param originalMessage the JMS request message 249 * @param extractedMessage the converted JMS request message, 250 * to be passed into the listener method as argument 251 * @return the name of the listener method (never {@code null}) 252 * @throws JMSException if thrown by JMS API methods 253 * @see #setDefaultListenerMethod 254 */ 255 protected String getListenerMethodName(Message originalMessage, Object extractedMessage) throws JMSException { 256 return getDefaultListenerMethod(); 257 } 258 259 /** 260 * Build an array of arguments to be passed into the target listener method. 261 * Allows for multiple method arguments to be built from a single message object. 262 * <p>The default implementation builds an array with the given message object 263 * as sole element. This means that the extracted message will always be passed 264 * into a <i>single</i> method argument, even if it is an array, with the target 265 * method having a corresponding single argument of the array's type declared. 266 * <p>This can be overridden to treat special message content such as arrays 267 * differently, for example passing in each element of the message array 268 * as distinct method argument. 269 * @param extractedMessage the content of the message 270 * @return the array of arguments to be passed into the 271 * listener method (each element of the array corresponding 272 * to a distinct method argument) 273 */ 274 protected Object[] buildListenerArguments(Object extractedMessage) { 275 return new Object[] {extractedMessage}; 276 } 277 278 /** 279 * Invoke the specified listener method. 280 * @param methodName the name of the listener method 281 * @param arguments the message arguments to be passed in 282 * @return the result returned from the listener method 283 * @throws JMSException if thrown by JMS API methods 284 * @see #getListenerMethodName 285 * @see #buildListenerArguments 286 */ 287 @Nullable 288 protected Object invokeListenerMethod(String methodName, Object[] arguments) throws JMSException { 289 try { 290 MethodInvoker methodInvoker = new MethodInvoker(); 291 methodInvoker.setTargetObject(getDelegate()); 292 methodInvoker.setTargetMethod(methodName); 293 methodInvoker.setArguments(arguments); 294 methodInvoker.prepare(); 295 return methodInvoker.invoke(); 296 } 297 catch (InvocationTargetException ex) { 298 Throwable targetEx = ex.getTargetException(); 299 if (targetEx instanceof JMSException) { 300 throw (JMSException) targetEx; 301 } 302 else { 303 throw new ListenerExecutionFailedException( 304 "Listener method '" + methodName + "' threw exception", targetEx); 305 } 306 } 307 catch (Throwable ex) { 308 throw new ListenerExecutionFailedException("Failed to invoke target method '" + methodName + 309 "' with arguments " + ObjectUtils.nullSafeToString(arguments), ex); 310 } 311 } 312 313}