Replacing MessageHandler metadata in Parsley to add priority

Tags: , ,

Lately I've been getting very intimate with Parsley. Recently I was working on an issue where I wanted my messageHandlers within Parsley to have some concept of priority.

As Parsley works right now MessageHandlers (as far as you're concerned) are handled in an arbitrary order when you dispatch a message. Behind the scenes there is a finite order that messages are run in - based on when they were registered - but you as a client of Parsley don't have any inherent control when declaring MessageHandler metadata as you would when say - adding an eventListener.

One of the beautiful parts about Parsley is that the author Jens Halm gives you hooks into replacing all of the IOC Kernel services.

There kernel services that can be replaced as listed on his site are as follows.

CompositeContextBuilder Responsible for processing the configuration, creating ObjectDefinitions, and then building and initializing the Context. May be fed with different types of ObjectDefinitionBuilders, like the builtin ones for ActionScript, MXML or XML configuration.
Context This is the core interface of the framework, putting all the other pieces together and delegating most of the work to the other parts of the kernel listed below. It allows you to pull objects out of the container or examine its contents.
ObjectDefinitionRegistry The registry for all ObjectDefinitions the Context will manage.
ObjectLifecycleManager Responsible for processing ObjectDefinitions, instantiating, configuring and disposing the actual instances described by those definitions.
ScopeManager Responsible for managing all scopes that are associated with a single Context.
MessageRouter The core interface of the Messaging Framework.
ViewManager Responsible for dynamically wiring views to the Context.

This gives you massive power of injecting behavior as you see fit into an already very powerful framework, and this is what I used in order to replace and extend the existing [MessageHandler] metadata tag.

First, as Jens describes on his site, each of the IOC kernel services has a factory that implements it's own interface. Since there are no hooks to remove metadata that has already been registered within Parsley and no way to natively prevent it from being added, I decided to replace the classes he was registering Metadata with.

This was a three step process:

First I created the factory that is used to make the built in MetadataDecoratorAssembler.

 
package com.appdivision.parsleyTest.kernel{
import flash.system.ApplicationDomain;
 
import org.spicefactory.parsley.core.context.provider.ObjectProviderFactory;
import org.spicefactory.parsley.core.factory.ObjectDefinitionRegistryFactory;
import org.spicefactory.parsley.core.registry.ObjectDefinitionRegistry;
import org.spicefactory.parsley.core.registry.impl.DefaultObjectDefinitionRegistry;
import org.spicefactory.parsley.core.scope.ScopeManager;
 
public class ReplacementDefinitionRegistryFactory implements ObjectDefinitionRegistryFactory {
 
public function create (domain:ApplicationDomain, scopeManager:ScopeManager,
	providerFactory:ObjectProviderFactory) : ObjectDefinitionRegistry {
	return new DefaultObjectDefinitionRegistry(domain, scopeManager,
		providerFactory, [new ReplacementMetadataDecoratorAssembler(domain)]);
	}
 
   }
}
 

The main purpose of this as I mentioned before was to replace the built in MetaDataDecoratorAssembler with my own version called ReplacementMetadataDecoratorAssembler.

There was one reason and one reason only I wanted to replace this class - to remove where the MessageHandler metadata was originally being registered and add in my own line

Removed

Metadata.registerMetadataClass(MessageHandlerDecorator, domain);

Added

Metadata.registerMetadataClass(PrioritizedMessageHandlerDecorator, domain);

By adding the PrioritizedMessageHandlerDecorator we are able to add a class that is nearly identical to what had been previously implemented with one added property - 'priority'. It is also this class' responsibility to create the PrioritizedMessageHandler which will eventually populate the list Parsley iterates through when invoking the method handlers of messages. This object is passed the priority which is where it ultimately ends up living. Before adding this in Parsley used to internally instantiate MessageHandlers - these lacked the priority property - which is why we had to create our own.

public var priority:int;

After going through these steps there are only a couple more things we have to do

Firstly we create a new factory - but this time we're replacing the default Message router factory.

 
package com.appdivision.parsleyTest.kernel
{
	import org.spicefactory.parsley.core.factory.MessageRouterFactory;
	import org.spicefactory.parsley.core.messaging.ErrorPolicy;
	import org.spicefactory.parsley.core.messaging.MessageRouter;
	import org.spicefactory.parsley.core.messaging.receiver.MessageErrorHandler;
 
	public class ReplacementMessageRouterFactory implements MessageRouterFactory
	{
	private var _errorHandlers:Array = new Array();
	private var _unhandledError:ErrorPolicy;
 
	public function ReplacementMessageRouterFactory()
	{
	}
	public function get unhandledError():ErrorPolicy
	{
		return _unhandledError;
	}
	public function set unhandledError(policy:ErrorPolicy):void
	{
		_unhandledError = policy;
	}
	public function addErrorHandler(target:MessageErrorHandler):void
	{
		_errorHandlers.push(target);
	}
	public function create () : MessageRouter {
		return new ReplacementMessageRouter(_errorHandlers, unhandledError);
	}
 
	}
}
 

You can see here we've added a line to create a ReplacementMessageRouter. This is important because inside the message router parsley internally creates message processors - we also need to replace this in order to process messages based on their priority.

Inside of the ReplacementMessageRouter I replace the processor with this line

var processor:MessageProcessor = new ReplacementMessageProcessor(message, messageType, selector, _receivers, _unhandledError);

Inside the message processor I simply add a sort function that i call within the constructor. This method sorts the array of PrioritizedMessageHandlers based on their priority property.

 
function Processor (receivers:Array, handler:Function, async:Boolean = true, handleErrors:Boolean = true) {
	this.receivers = receivers;
	sort();
	this.handler = handler;
	this.async = async;
	this.handleErrors = handleErrors;
}
internal function sort():void{
	receivers.sortOn('priority', [Array.NUMERIC]);
}
 

Finally there is just one more step to set all of this in motion. Just before calling build() on your contextBuilder.

 
GlobalFactoryRegistry.instance.definitionRegistry = new ReplacementDefinitionRegistryFactory();
GlobalFactoryRegistry.instance.messageRouter = new ReplacementMessageRouterFactory();
FlexContextBuilder.build(Context, this);
 

When this is all done you can add message handlers to your methods with priority and Parsley will do it in that order.

[MessageHandler(priority = 0)]

All MessageHandlers without priority will be handled after ones that specify it.

Here is the source of what I did and a very simple example.

Debugging Parsley Messaging Errors

Tags: , , ,

When first starting to use Parsley the black-magic that is happening behind the scenes may confound some people.
Parsley purposely swallows runtime errors in order to not disrupts its messaging flow. You may see something like

 
DefaultMessageProcessor Message Target threw Error
 

The author of Parsley Jens Halm explains his reasoning behind this in this thread on the spicefactory forums

In larger application where Parsley messaging are kicking off complicated routines - the lack of detail that Parsley logs when swallowing errors can be prohibitive in finding the source of the Error - and if you don't know to look at your console the Errors will be effectively failing silently.

In Parsley 2.1 Jens introduced the [MessageError] metadata. This can be applied globally to all Errors that happen during the flow of a Parsley message or on a selector basis.

In our application we recently implemented a way through this new hook to catch and throw these errors - giving you valuable stack trace information - ultimately revealing the source of your Error. This makes it much easier to track down the source

 
package
{
        import org.spicefactory.parsley.core.messaging.MessageProcessor;
 
        public class ParsleyErrorBroker
        {
	    [MessageError]
	    public function handleError (processor:MessageProcessor, error:Error):void {
                throw error;
            }
        }
    }
}
 

After writing this class you have to add the following to your compiler arguments

 
-keep-as3-metadata=MessageError
 

Add this class to your Parsley Context - and you're set- after that all Errors will be routed to this class and thrown.

If you have any questions feel free to post below