11 Extending the Framework

All previous chapters demonstrated the Parsley feature set from the perspective of an application developer. But since Parsley provides such basic IOC capabilites like Dependency Injection or fully decoupled Messaging it is equally important that the framework is easy to extend. If you are going to build your own framwork, whether inhouse or Open Source, you wouldn't want to reinvent the wheel for these basic features and prefer to integrate with existing solutions.

So extensibility is an area a lot of thought has been put into. And hopefully when starting to build your own extensions you will enjoy the huge number of available extensions points, the flexibility and the ease of use. One extension point where Parsley particularly shines is the ObjectDefinitionDecorator interface which allows you to write a single extension class and use it as a custom Metadata, MXML or XML configuration tag!

When browsing the Parsley code base you'll notice that almost everything in the internal API lives behind interfaces. This makes it easy to create your own implementations for a particular aspect of the framework and switch it while still using the default implementations for other internal services.

11.1 Available Extension Points

Let's start with a quick overview over the extensible parts of the framework.

Factories

That's the only feature we already covered in 6.1 Using Factories. Instead of configuring an object directly you can configure a factory that produces the final target instance.

The ObjectDefinitionDecorator interface for creating custom configuration tags

Probably the most important extension point and the most powerful one. This interface allows you to create extensions that process custom configuration tags. And as already mentioned in the introduction, you can write them in a way that a single implementation can be used as a Metadata, MXML or XML tag. See 11.2 Creating Custom Configuration Tags for details.

The RootConfigurationTag interface for creating tags that produce objects

These tags do not produce the target objects directly, but instead they produce ObjectDefinition instances which the container uses to produce the final object. This extension point may come in handy when you need a higher level of abstraction than provided by simple factories. See 11.3 Creating Tags that produce Objects for details. Alternatively you may even implement this interface for special kinds of root configuration tags that do not represent object definitions but rather perform some other initialization.

The FactoryRegistry

Finally all central services of the Parsley IOC kernel live behind interfaces and can be replaced. These are - amongst others - the ViewManager, the ScopeManager and the central Context interface. The mechanism is described in 11.6 Replacing IOC Kernel Services.

The ConfigurationProcessor interface for creating custom configuration processors

Implementations of this interface process configuration artifacts and build the ObjectDefinitionRegistry that the default Context implementation uses internally. Parsley contains builtin ConfigurationProcessors for processing MXML, XML or ActionScript configuration. You can easily create your own implementations and then combine them with the builtin ones using the CompositeContextBuilder.

Examples for a custom builder would be configuration obtained from a property file, through a Socket connection or using a WebService call. Or it may be a builder that programmatically constructs part of the registry. See 11.7 Custom Configuration Mechanisms for details.

11.2 Creating Custom Configuration Tags

As this is the main extension point we will discuss it in greater detail and also provide an example implementation.

11.2.1 The ObjectDefinitionDecorator interface

The interface is quite simple, it consists of a single method:

public interface ObjectDefinitionDecorator {
    
    function decorate (definition:ObjectDefinition, registry:ObjectDefinitionRegistry) 
                                                                         : ObjectDefinition;
    
}

The method will be invoked by the container for each configuration tag it encounters for an object that was added to the container. It doesn't matter whether it is a builtin configuration tag or a custom extension tag, or whether it is a metadata tag, an MXML or XML tag. As long as the tag is mapped to a class that implements this interface the container will invoke it for each tag on each object.

The definition parameter it passes to the decorator is the ObjectDefinition that it currently processes. In most custom tag implementations you will modify this definition, specifying constructor arguments, property values, object lifecycle listeners or instantiators.

The second parameter is the ObjectDefinitionRegistry currently under construction. In most cases you'll just ignore this parameter, but under certain circumstances your custom tag may wish to create new definitions (in addition to the one it modifies) and add them to the registry. Those additional definitions might describe collaborators that the modified definition will need to operate for example. Of course you should check if this collaborator has been already added as the custom tag may be placed on multiple objects.

Finally this method has to return an ObjectDefinition. In most cases this will be the same instance that was passed to the decorate method. But you are allowed to replace it with a completely new definition instance. Since ObjectDefinition is an interface it may even be a custom definition implementation. But in such a case you should make sure that you copy the configuration artifacts already added to the definition as other tags may have already processed this definition.

11.2.2 Builtin decorator implementations

It might be interesting to know that we actually eat our own dog food. Every configuration tag that exists in Parsley implements this interface, whether it's the basic [Inject] tag or the [MessageHandler] tag or anything else. So as a consequence you can not only add new configuration tags to Parsley, you could even replace the existing ones if you want to modify their behaviour. Furthermore you can use all the existing implementations as examples. You'll find the builtin tag implementations in the following packages:

11.2.3 The Singleton Tag Example

We will now implement a simple decorator extension. Let's first describe the use case for the extension: In all examples you have seen in the manual there were actually two choices how the container instantiates an object: It either creates it with new, optionally performing Constructor Injection, or it encounters a method marked as [Factory] in which case it calls this method and uses the returned object.

While this covers a great number of use cases you may come into a situation where you have to work with an existing class that was created using the Singleton pattern. In this case there would be a static property or static method that holds the singleton instance. The two mechanisms described above would not work in this scenario.

In this chapter we will create an extension that you could use as a [Singleton] metadata tag above the class declaration or alternatively as a tag in MXML or XML configuration. We would even consider to add this to the Parsley release, but we are a bit reluctant as we don't want to advocate the use of the Singleton pattern. When using an IOC Container this pattern is obsolete and you can simply avoid all the potential problems associated with it. If you think it should be added to the core, please let us know in the forum!

11.2.4 Implementing the Singleton Tag

This is the full implementation for the tag extension:

package org.spicefactory.parsley.samples {

import org.spicefactory.lib.reflect.ClassInfo;
import org.spicefactory.lib.reflect.Method;
import org.spicefactory.lib.reflect.Property;
import org.spicefactory.parsley.core.errors.ContextError;
import org.spicefactory.parsley.registry.ObjectDefinition;
import org.spicefactory.parsley.registry.ObjectDefinitionDecorator;
import org.spicefactory.parsley.registry.ObjectDefinitionRegistry;


[Metadata(types="class")]
public class Singleton implements ObjectDefinitionDecorator {
    
    
    public var property:String;
    
    public var method:String;



    public function decorate (definition:ObjectDefinition, registry:ObjectDefinitionRegistry) 
                                                                          : ObjectDefinition {
        if (property != null && method != null) {
            throw new ContextError("Only one of 'method' or 'property' may be specified for " 
                    + definition);
        }
        if (property != null) {
            var p:Property = definition.type.getStaticProperty(property);
            if (p == null) {
                throw new ContextError("Class " + definition.type.name 
                        + " does not contain a property with name " + property);
            }
            if (!p.readable) {
                throw new ContextError(p.toString() + " is not readable");
            }
            definition.instantiator = new SingletonPropertyInstantiator(p);
        }
        else {
            if (method == null) method = "getInstance";
            var m:Method = definition.type.getStaticMethod(method);
            if (m == null) {
                throw new ContextError("Class " + definition.type.name 
                        + " does not contain a method with name " + method);
            }
            if (m.parameters.length > 0) {
                throw new ContextError(m.toString() + " requires method parameters");
            }            
            definition.instantiator = new SingletonMethodInstantiator(m);
        }
    }
}
}

import org.spicefactory.lib.reflect.Method;
import org.spicefactory.lib.reflect.Property;
import org.spicefactory.parsley.core.Context;
import org.spicefactory.parsley.registry.ObjectInstantiator;

class SingletonPropertyInstantiator implements ObjectInstantiator {
    
    private var property:Property;
    
    function SingletonPropertyInstantiator (p:Property) {
        this.property = p;
    }
    
    public function instantiate (context:Context) : Object {
        return property.getValue(null);
    }
    
}

class SingletonMethodInstantiator implements ObjectInstantiator {
    
    private var method:Method;
    
    function SingletonMethodInstantiator (m:Method) {
        this.method = m;
    }
    
    public function instantiate (context:Context) : Object {
        return method.invoke(null, []);
    }
    
}

Let's examine the implementation piece by piece. First we define two properties: method and property since we want to allow the class to provide the singleton either through a static method or a static property. The value for these properties will be set by the framework, automatically mapping the attributes of the tag to the properties. This would include type conversion if necessary (e.g. to int or to Class). Parsley will map the attributes from metadata tags, MXML and XML tags in a similar way, thus you only have to create a single extension implementation that can be used with all of these configuration mechanisms.

The decorate method primarily consists of sanity checks. You should try to be conservative and check the validity of the configuration as early as possible. If you throw an Error the framework will catch it and continue processing other configurations, avoiding fail-fast behavious, so that it only throws a final Error with all collected Errors that any decorator had thrown. This way application developers can fix configuration errors in one go.

The real work happens in the two lines highlighted in red: Here we modify the provided ObjectDefinition instance and set an ObjectInstantiator implementation. This tells the framework not to create the instance by simply using new, but to use the provided instantiator instead. Depending on whether a property name or method name was provided we create instances of a corresponding ObjectInstantiator implementation that we created as private classes within the same source file.

We can reflect on the type of the class that we are about to configure through the type property of the definition passed to the decorate method. This property holds an instance of ClassInfo, a class from the Spicelib Reflection API.

We now have completed the implementation. We finally have to tell the framework about it so that we can use it as a Metadata, MXML or XML tag. That's covered in the following three sections.

11.2.5 Using it as a Metadata Tag

The Parsley support for metadata configuration tags is built on top of the Spicelib Reflection API which offers the capability to map attributes of metadata tags to properties of classes, bringing AS3 metadata a bit closer to the type-safe nature of Java Annotations for example. See 17.8 Mapping classes to metadata tags in the Spicelib Manual for details.

Making the custom tag available for Metadata configuration requires three steps:

1. Add the [Metadata] tag to the class declaration:

[Metadata(types="class")]
public class Singleton implements ObjectDefinitionDecorator {

With the types attribute we specify on which declarations we want the tag to be processed. In this case we only allow it on the class declaration. Other tags may be placed on properties or methods as well. In case you wonder why we don't allow the tag to be put directly on the static method that provides the singleton: Parsley does not process metadata on static properties or static methods.

2. Add the class to the Spicelib Metadata Support:

Metadata.registerMetadataClass(Singleton);

This must happen before you create your first Parsley Context.

3. Add the metadata tag to mxmlc or compc compiler arguments:

-keep-as3-metadata+=Singleton

If you create a reusable library containing Parsley tag extensions it is recommended to compile the library into an SWC. In this case you no longer have to explicitly add the compiler flag for applications that use this SWC as they will be automatically included for all applications that use this SWC. In this case you only have to provide the compiler argument to compc when creating the SWC.

After these three steps you can start using the tag on classes that you add to the IOC Container:

[Singleton(method="getDefaultManager")]
public class UserManager {

Metadata on Properties or Methods

In our example we create a metadata tag that can only be place on the class declaration. If you create tags for properties or methods there is one additional thing to consider: How you can determine the property or the method the tag was placed on. This is actually quite easy: Create a property of type String marked with the [Target] metadata tag:

[Target]
public var property:String;

With that tag placed on the property the framework will automatically set the value of this property to the name of the property it was placed on. Of course only if it was used as a metadata tag. When using the same tag as an MXML or XML tag the user has to provide the value as an attribute of the tag:

<MySpecialTag property="foo"/>

11.2.6 Using it as an MXML Tag

This is the easiest part. In fact there is nothing you need to do here. You can use the decorator class as an MXML tag in your Parsley configuration like any other AS3 class can be used in MXML:

<Object id="userManager" type="{UserManager}"/>
    <myprefix:Singleton method="getDefaultManager"/>
</Object>

Just map the package the decorator was created in to a prefix and use the tag like shown above within an object definition. If you create a set of reusable configuration tags living in separate packages it is recommended to create a manifest file for an MXML namespace for the tags.

11.2.7 Using it as an XML Tag

Finally you may also want to use this tag in external XML configuration files. For this purpose you have to create a custom configuration namespace and add any number of decorator tags:

var uri:String = "http://www.bookstore.com/config";
var ns:XmlConfigurationNamespace = XmlConfigurationNamespaceRegistry.registerNamespace(uri);
ns.addDefaultDecoratorMapper(Singleton, "singleton");
// ... add other tags to the namespace

The XML configuration support is built on top the Spicelib XML-to-Object-Mapper Module (see 18 XML to Object Mapper for details). The addDefaultDecoratorMapper method creates a mapper for your tag that simply maps the attribute values of the tags to the property values of the specified class. For decorators this is sufficient in most cases. For more advanced stuff you could use the other addXXX methods of the XmlConfigurationNamespace class.

You can then use tags from this namespace in XML configuration files:

<objects 
    xmlns="http://www.spicefactory.org/parsley" 
    xmlns:bookstore="http://www.bookstore.com/config">

    <object id="userManager" type="com.bookstore.managers.UserManager"/>
        <bookstore:singleton method="getDefaultManager"/>
    </object>   
</objects>

11.3 Creating Tags that produce Objects

In our example extension we created in the previous sections we showed how to implement a custom tag that configures an object. Sometimes you may want to create tags that actually create objects and not only add a particular configuration artifact to an existing definition.

11.3.1 Plain Object Factories

In such a case the first thing you should consider is creating a plain factory, i.e. an object that contains a method annotated with [Factory]. This feature is described in 6.1 Using Factories. Such a class would be usable in MXML configuration out-of-the-box (just like any class can be used in MXML - see the examples in the referenced chapter). If you want to use it in XML configuration files you could create a custom configuration namespace that includes this factory in a simlilar way you did with the decorator tag in the previous section:

var uri:String = "http://www.bookstore.com/config";
var ns:XmlConfigurationNamespace = XmlConfigurationNamespaceRegistry.registerNamespace(uri);
ns.addDefaultObjectMapper(CategoryFactory, "category");
// ... add other tags to the namespace

You can then use the tag in a very similar way like you would use it in MXML.

11.3.2 The RootConfigurationTag interface

In some cases such a plain factory might not be sufficient. For example the factory method must specify the exact return type so that the framework can reflect on the type produced by the factory:

[Factory]
public function createCategory () : Category {

If the type has to be determined at runtime (when processing the configuration) this would not be possible. In this case you can add one more level of indirection and not create a factory that creates objects but instead create a factory that produces object definitions. For that purpose you may implement the RootConfigurationTag interface:

public interface RootConfigurationTag {
	
    function process (registry:ObjectDefinitionRegistry) : void;
	
}

This method is invoked for tags that are placed on the top level of MXML or XML configuration files.

An implementation of that interface may include a property that allows the user to specify the type (along with an arbitrary number of additional properties):

public class Service implements RootConfigurationTag {

    public var type:Class;
    
    // ... other properties automatically mapped from tag attributes
    
    public function process (registry:ObjectDefinitionRegistry) : void; {
        var definition:RootObjectDefinition = registry.builders
                .forRootDefinition(type)
				.id(id)
				.lazy(lazy)
				.singleton(singleton)
				.order(order)
				.decorators(decorators)
				.buildAndRegister();
		/* All methods shown above are optional.
           Now the definition already contains all artifacts 
           produced by metadata tags on the class
           But you may also do some custom processing with the definition */
    }
    
}

Now the type can be determined dynamically or provided by the user. It will be passed to the forRootDefinition methos. Using this tag also means that standard and custom metadata tags will be processed. We can then add any required custom logic for producing the final definition (setting property values, adding lifecycle listeners and so on).

Using the root tag in MXML

Like always the custom tag can be used in MXML without further setup:

<mx:Object 
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:config="com.bookstore.config.*">
    
    <config:Service type="{MyCartService}"/>
    
    <!-- other object defintions -->
        
</mx:Object> 

The nice thing about that use case is that although the user/application provides the type information which will be processed at runtime, the services produced by the factory are still available for Dependency Injection by type in other objects:

[Inject]
public var service:CartService;

Using the root tag in XML

For XML files you have to add the factory to a custom XML configuration namespace like this:

var uri:String = "http://www.bookstore.com/config";
var ns:XmlConfigurationNamespace = XmlConfigurationNamespaceRegistry.registerNamespace(uri);
ns.addDefaultFactoryMapper(Service, "service");
// ... add other tags to the namespace

You can then use it in XML files:

<objects 
    xmlns="http://www.spicefactory.org/parsley" 
    xmlns:bookstore="http://www.bookstore.com/config">

    <bookstore:service type="com.bookstore.service.MyCartService"/>
</objects>

11.4 Scope-wide Managers

In some cases you may want to create custom configuration tags that interact with some kind of central manager in a similar way like the builtin message receiver tags interact with the MessageReceiverRegistry for example. You can do this by registering a scope extension, either globally or (in rare cases) for a single Context only.

First you have to create a factory that creates your scope-wide manager instances. It simply has to implement the ScopeExtensionFactory interface:

interface ScopeExtensionFactory {

    function create () : Object;
    
}

You can then register this factory for a particular scope like this:

var factory:ScopeExtensionFactory = ...;
GlobalFactoryRegistry.instance.scopeExtensions.addExtension(factory, ScopeName.GLOBAL);

In the example above the extension would then be available in the global scope. A really powerful mechanism is registering an extenstion for all scopes, simply by omitting the scope parameter:

var factory:ScopeExtensionFactory = ...;
GlobalFactoryRegistry.instance.scopeExtensions.addExtension(factory);

This way a new instance of your scope-wide manager will be created for each scope, the global scope, each local scope of each Context and each custom scope. This way you could even create custom configuration tags that offer a scope attribute in a similar way like the MessageHandler or MessageInterceptor tags. It is then up to the user to chose the scope this configuration option should be applied to.

A sample snippet for an ObjectDefinitionDecorator might look like this:

public class EnterScreenDecorator implements ObjectDefinitionDecorator {

    public var scope:String = ScopeName.GLOBAL;
    
    [...]
    
    public function decorate (definition:ObjectDefinition, 
                                        registry:ObjectDefinitionRegistry) : ObjectDefiniton {
        var scope:Scope = registry.scopeManager.getScope(scope);
        var navigationManager:NavigationManager 
                = scope.extensions.byType(NavigationManager) as NavigationManager;
        var navigationManager.doSomethingWithDefinition(definition);
    }
    
}

In the example above the default global scope could be overwritten by the user through the scope property. Of course your manager does not necessarily have to deal with definitions directly. You might also add lifecycle listeners to the definition and then pass the actual managed object to the manager after it was created.

11.5 Initializing Extension Modules

When you create a set of configuration tags and some scope managers that integrate with them, you may want to offer your users a simple one-liner as a means to activate that extension:

NavigationSupport.initialize();

If you also offer your tags in an XML variant built on top of the custom configuration namespace support in Parsley you may want to prefer to offer them with a separate initializer method. This way you would make sure that users who do not use XML configuration do not end up with the entire Spicelib XML-Object-Mapper framework compiled into their SWF.

NavigationSupport.initialize();
NavigationXmlSupport.initialize();

In the example above XML support is activated explicitly in addition to the core extension module.

For use in Flex applications you may prefer to implement the ContextBuilderProcessor interface instead, so that you can use that class as a child tag of a <ContextBuilder> tag:

<parsley:ContextBuilder config="{MyConfig}">
    <app:NavigationSupport/>
</parsley:ContextBuilder>

In the implementation of that initializer tag you then usually register all configuration tags and scope-wide extensions used by the module:

public class NavigationSupportTag implements ContextBuilderProcessor {

    public function processBuilder (builder:CompositeContextBuilder) : void {
        Metadata.registerMetadataClass(EnterScreenDecorator);
        Metadata.registerMetadataClass(ExitScreenDecorator);
        var factory:ScopeExtensionFactory = new NavigationManagerFactory();
        GlobalFactoryRegistry.instance.scopeExtensions.addExtension(factory);
    
        [...]
    }
}

11.6 Replacing IOC Kernel Services

All central services of the IOC kernel live behind interfaces which can be replaced by custom implementations. The replacement can be done globally or just for a single Context, in case you build a modular application with a hierarchy of multiple Contexts.

In the following example the central ViewManager will be replaced globally by specifying a custom ViewManagerFactory:

GlobalFactoryRegistry.instance.viewManager = new MyCustomViewManagerFactory();

A dedicated factory interface exists for each of the seven kernel services. You cannot specify the ViewManager instance directly, because a new instance will be created for each new Context. If you want to replace the ViewManager for a single Context only, you can specify the factory on the CompositeContextBuilder:

var viewRoot:DisplayObject = ...;
var builder:CompositeContextBuilder = new DefaultCompositeContextBuilder(viewRoot);
builder.factories.viewManager = new MyCustomViewManagerFactory();
FlexContextBuilder.merge(BookStoreConfig, builder);
builder.build();

For reusable replacements to be used in Flex applications it is recommended to create a simple custom tag like shown in the preceding section that can then be used as a child tag in a ContextBuilder tag.

List of IOC Kernel Services

CompositeContextBuilder Responsible for processing the configuration, creating ObjectDefinitions, and then building and initializing the Context. May be fed with different types of ConfigurationProcessors, 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.

All the services listed above can be replaced through the FactoryRegistry.

11.7 Custom Configuration Mechanisms

Although Parsley is already quite flexible in how you configure objects with support for configuration with Metadata, MXML, XML or ActionScript, you may require even more freedom in how you create object definitions. You may want to process configuration loaded through Sockets or WebServices or simply programmatically create some object definitions. For this purpose you can implement the ConfigurationProcessor interface. The interface contains a single method:

public interface ConfigurationProcessor {
	
    function processConfiguration (registry:ObjectDefinitionRegistry) : void;
	
}

Your implementation of this interface may create any number of ObjectDefinition instances and add them to the registry provided by the framework. If you still want to process the metadata tags of the classes you add to the registry (in addition to your own logic of constructing definitions) you should use the mechanism provided by the framework:

var type:Class = AddToCartAction;
var id:String = "addToCartAction";
var definition:RootObjectDefinition = registry.builders
        .forRootDefinition(type)
        .id(id)
        .buildAndRegister();
/* Now the definition already contains all artifacts 
   produced by metadata tags on the class */
/* Now you may do some custom processing with the definition */

Your custom builder can then be combined with any existing builder, using the CompositeContextBuilder:

var viewRoot:DisplayObject = ...;
var builder:CompositeContextBuilder = new DefaultCompositeContextBuilder(viewRoot);
FlexContextBuilder.merge(BookStoreConfig, builder);
XmlContextBuilder.merge("logging.xml", builder);
builder.addProcessor(new MyCustomConfigurationProcessor());
builder.build();

After the code above executed the objects defined with MXML and XML happily coexist with objects added through your custom builder in the same Context.

Asynchronous builders

If your builder operates asynchronously you have to implement the AsyncConfigurationProcessor interface instead (which extends the ConfigurationProcessor interface). This might be necessary if you obtain the configuration with some remote service call for example. The interface looks like this:

[Event(name="complete", type="flash.events.Event")]

[Event(name="error", type="flash.events.ErrorEvent")]

public interface AsyncConfigurationProcessor 
                                      extends ConfigurationProcessor, IEventDispatcher {
	
    function cancel () : void;
	
}

First you must be prepared that your asynchronous builder may get cancelled (for example if the application destroys the associated Context before it was fully configured). Finally you have to throw either Event.COMPLETE or ErrorEvent.ERROR, depending on whether configuration suceeded or failed.