SPRNET-1363 - Refactor OnMessage processing in MessageListenerAdapter to be better support listener method dispatching logic in subclasses.

This commit is contained in:
markpollack
2010-09-06 20:31:12 +00:00
parent 52ebf1c17d
commit 71eddc839d
6 changed files with 191 additions and 6 deletions

View File

@@ -0,0 +1,52 @@
#region License
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Exception to be thrown when the execution of a listener method failed.
/// </summary>
/// <author>Juergen Hoeller</author>
/// <author>Mark Pollack (.NET)</author>
public class ListenerExecutionFailedException : MessagingException
{
/// <summary>
/// Initializes a new instance of the <see cref="ListenerExecutionFailedException"/> class, with the specified message
/// </summary>
/// <param name="message">The message.</param>
public ListenerExecutionFailedException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ListenerExecutionFailedException"/> class, with the specified message
/// and root cause exception
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public ListenerExecutionFailedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -18,6 +18,7 @@
#endregion
using System;
using System.Collections;
using System.Messaging;
using Common.Logging;
@@ -191,10 +192,20 @@ namespace Spring.Messaging.Listener
get { return defaultHandlerMethod; }
set {
defaultHandlerMethod = value;
processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
ProcessingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
}
}
/// <summary>
/// Gets or sets the processing expression for use in custom subclasses
/// </summary>
/// <value>The processing expression.</value>
protected IExpression ProcessingExpression
{
get { return processingExpression; }
set { processingExpression = value; }
}
#region IInitializingObject Members
/// <summary>
@@ -384,11 +395,16 @@ namespace Spring.Messaging.Listener
{
object convertedMessage = ExtractMessage(message);
IDictionary vars = new Hashtable();
vars["convertedObject"] = convertedMessage;
//IDictionary vars = new Hashtable();
//vars["convertedObject"] = convertedMessage;
string methodName = GetHandlerMethodName(message, convertedMessage);
object[] listenerArguments = BuildListenerArguments(convertedMessage);
object result = InvokeListenerMethod(methodName, listenerArguments);
//Invoke message handler method and get result.
object result = processingExpression.GetValue(handlerObject, vars);
//object result = processingExpression.GetValue(handlerObject, vars);
if (result != null)
{
HandleResult(result, message);
@@ -412,12 +428,55 @@ namespace Spring.Messaging.Listener
return DefaultHandlerMethod;
}
/// <summary>
/// Builds an array of arguments to be passed into the taret listener method.
/// </summary>
/// <remarks>
/// Allows for multiple method arguments to be built from a single message object.
/// <p>The default implementation builds an array with the given message object
/// as sole element. This means that the extracted message will always be passed
/// into a <i>single</i> method argument, even if it is an array, with the target
/// method having a corresponding single argument of the array's type declared.</p>
/// <p>This can be overridden to treat special message content such as arrays
/// differently, for example passing in each element of the message array
/// as distinct method argument.</p>
/// </remarks>
/// <param name="convertedMessage">The converted message.</param>
/// <returns>the array of arguments to be passed into the
/// listener method (each element of the array corresponding
/// to a distinct method argument)</returns>
protected virtual object[] BuildListenerArguments(object convertedMessage)
{
return new object[] { convertedMessage };
}
/// <summary>
/// Invokes the specified listener method. This default implementation can only handle invoking a
/// single argument method.
/// </summary>
/// <param name="methodName">Name of the listener method.</param>
/// <param name="arguments">The arguments to be passed in. Only the first argument in the list is currently
/// supported in this implementation.</param>
/// <returns>The result returned from the listener method</returns>
protected virtual object InvokeListenerMethod(string methodName, object[] arguments)
{
IDictionary vars = new Hashtable();
vars["convertedObject"] = arguments[0];
if (methodName.CompareTo(DefaultHandlerMethod) != 0)
{
//This is just to handle the case of overriding GetHandlerMethodName in a subclass and nothing else.
return ExpressionEvaluator.GetValue(handlerObject, methodName + "(#convertedObject)", vars);
}
// the normal case of using the cached expression.
return processingExpression.GetValue(handlerObject, vars);
}
/// <summary>
/// Initialize the default implementations for the adapter's strategies.
/// </summary>
protected virtual void InitDefaultStrategies()
{
processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
ProcessingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
}
/// <summary>
@@ -436,7 +495,7 @@ namespace Spring.Messaging.Listener
return message;
}
private void HandleResult(object result, Message request)
protected virtual void HandleResult(object result, Message request)
{
if (logger.IsDebugEnabled)
{

View File

@@ -49,6 +49,7 @@
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Messaging\Core\MessageQueueMetadata.cs" />
<Compile Include="Messaging\Core\MessageQueueMetadataCache.cs" />
<Compile Include="Messaging\Listener\ListenerExecutionFailedException.cs" />
<Compile Include="Messaging\Support\Converters\MessageConverterCreatorDelegate.cs" />
<Compile Include="Messaging\Core\DefaultMessageQueueFactory.cs" />
<Compile Include="Messaging\Support\MessageQueueCreatorDelegate.cs" />

View File

@@ -0,0 +1,69 @@
#region License
/*
* Copyright 2002-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region
using System;
using System.Reflection;
using Spring.Expressions;
using Spring.Util;
#endregion
namespace Spring.Messaging.Listener.Adapter
{
/// <summary>
/// POC for a standard reflection based listener method invoker.
/// </summary>
/// <author>Mark Pollack</author>
public class StandardReflectionMessageListenerAdapter : MessageListenerAdapter
{
private const BindingFlags BINDING_FLAGS
= BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Instance | BindingFlags.Static
| BindingFlags.IgnoreCase;
protected override object InvokeListenerMethod(string methodName, object[] arguments)
{
try
{
MethodInfo mi = MethodNode.GetBestMethod(HandlerObject.GetType(), methodName, BINDING_FLAGS, arguments);
if (mi == null)
{
throw new ListenerExecutionFailedException("Failed to invoke the target method '" + methodName +
"' with arguments " +
StringUtils.ArrayToCommaDelimitedString(arguments));
}
try
{
return mi.Invoke(HandlerObject, arguments);
} catch (Exception e)
{
throw new ListenerExecutionFailedException("Listener method '" + methodName + "' threw exception.", e);
}
} catch (Exception e)
{
throw new ListenerExecutionFailedException("Failed to invoke the target method '" + methodName +
"' with arguments " +
StringUtils.ArrayToCommaDelimitedString(arguments), e);
}
}
}
}

View File

@@ -69,7 +69,10 @@
<!-- Adapter -->
<!--
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging">
-->
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.Adapter.StandardReflectionMessageListenerAdapter, Spring.Messaging.Tests">
<property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/>
<property name="MessageConverterObjectName" value="binaryMessageConverter"/>
<property name="HandlerObject" ref="simpleHandler"/>

View File

@@ -90,6 +90,7 @@
<ItemGroup>
<Compile Include="MessagingCompilerOptionsTests.cs" />
<Compile Include="MessagingExceptionTests.cs" />
<Compile Include="Messaging\Listener\Adapter\StandardReflectionMessageListenerAdapter.cs" />
<Compile Include="Messaging\Listener\WaitingHandler.cs" />
<Compile Include="Messaging\Core\MessageQueueMetadataCacheTests.cs" />
<Compile Include="Messaging\Core\MessageQueueUtils.cs" />