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 ObjectDefinitionFactory interface for creating tags that produce objects

Those are different than the factories mentioned above. They 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.

The ObjectDefinitionBuilder 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 ObjectDefinitionBuilders 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.4 Custom Configuration Processors for details.

Multiple other interfaces

As already mentioned almost all pieces of the core container logic live behind interfaces which can be replaced by custom implementations. The need to replace core parts of the framework may arise only for rather exotic use cases, but nevertheless it's always an advantage when the internal architecture is as modular and decoupled as possible. We won't cover these more exotic use cases in the manual, but rather list the available interfaces here. For details you can browse the API docs.

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.getProperty(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.getMethod(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 16.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 17 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 ObjectDefinitionFactory 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 ObjectDefinitionFactory interface:

public interface ObjectDefinitionFactory {
	
    function createRootDefinition (registry:ObjectDefinitionRegistry) : RootObjectDefinition;	

    function createNestedDefinition (registry:ObjectDefinitionRegistry) : ObjectDefinition;	
	
}

The first method is invoked for tags that are placed on the top level of MXML or XML configuration files. The second is invoked for inline object definitions as described in 4.5 Declaring Dependencies in MXML or XML (right at the end, the final section titled "Declaring dependencies inline").

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 ServiceFactory implements ObjectDefinitionFactory {

    public var type:Class;
    
    // ... other properties automatically mapped from tag attributes
    
    public function createRootDefinition (registry:ObjectDefinitionRegistry) 
                                                                    : RootObjectDefinition {
        var factory:ObjectDefinitionFactory = new DefaultObjectDefinitionFactory(type);
        var definition:RootObjectDefinition = factory.createRootDefinition(registry);
        /* Now the definition already contains all artifacts 
           produced by metadata tags on the class */
        /* Now you may do some custom processing with the definition */
        return definition;
    }
    
    public function createNestedDefinition (registry:ObjectDefinitionRegistry) 
                                                                    : ObjectDefinition {
        /* usually this implementation is similar like that for root definitions */
    }

}

Now the type can be determined dynamically or provided by the user. It will be passed to the constructor of the DefaultObjectDefinitionFactory. Using this factory 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 factory 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:ServiceFactory 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 factory 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(ServiceDefintionFactory, "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 Custom Configuration Processors

Although Parsley is already quite flexible in how you configure objects with support for configuration with Metadata, MXML, XML or ActionScript (in fact it is way more flexible than any Flex/Flash IOC Container I have seen so far), 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 ObjectDefinitionBuilder interface. The interface contains a single method:

public interface ObjectDefinitionBuilder {
	
    function build (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 DefaultObjectDefinitionFactory provided by the framework:

var type:Class = AddToCartAction;
var id:String = "addToCartAction";
var factory:ObjectDefinitionFactory = new DefaultObjectDefinitionFactory(type, id);
var definition:RootObjectDefinition = factory.createRootDefinition(registry);
/* Now the definition already contains all artifacts 
   produced by metadata tags on the class */
/* Now you may do some custom processing with the definition */
registry.registerDefinition(definition);

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

var builder:CompositeContextBuilder = new CompositeContextBuilder();
FlexContextBuilder.merge(BookStoreConfig, builder);
XmlContextBuilder.merge("logging.xml", builder);
builder.addBuilder(new MyCustomDefinitionBuilder());
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 AsyncObjectDefinitionBuilder interface instead (which extends the ObjectDefinitionBuilder 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 AsyncObjectDefinitionBuilder 
                                      extends ObjectDefinitionBuilder, 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.