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