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.
Let's start with a quick overview over the extensible parts of the framework.
[Inject] or [MessageHandler]. <Object> or <DynamicCommand>. ViewManager, the MessageRouter and the core
Context interface. AsyncToken and the Spicelib Task Framework. Other sections in this chapter deal with information about internal APIs that are useful for more than just one of the extension points listed above:
Finally there are further features of the framework that do not even require to directly talk to the framework API or implement framework interfaces, but nevertheless may be often used in a way similar to other types of extensions. These are:
The two links above both point to the lifecycle chapter, these features are not discussed in this chapter.
Being one of the most widely used extension points it allows to create custom metadata you can add
to any object managed by Parsley. Like with most other extension points all the builtin tags of the core framework
like [Inject] or [MessageHandler] are implemented as extensions themselves.
Every custom metadata tag has to implements a simple interface with just one method:
public interface ObjectDefinitionDecorator {
function decorate (builder:ObjectDefinitionBuilder) : void;
}
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 builder parameter it passes to the decorator can be used to specify configuration options for the
object definition it currently processes.
A custom metadata tag usually performs one or both of the following two tasks:
ObjectDefinitionBuilder instance passed to the decorator gives convenient access to core features,
like specifying injections to be performed or registering message receivers. See 12.4 Working with ObjectDefinitions
for more details. ObjectProcessor that configures each instance created from a definition.
See 12.2.4 The ObjectProcessor Interface for details. For your own applications you usually implement two types of custom metadata tags. One type is a fully generic, reusable extension. In this case you are of course invited to blog about it, post on our forum or contribute the tag to the framework, if you think that it is generally useful. The other type of tag would be specific to your application. Even if it is just reused in a few areas of a single application, a tag may very well help removing a lot of unnecessary plumbing.
As the builtin tags all use this extension point themselves, you can examine these implementations
as a starting point for you own tags. Most of the tag implementations reside in the parsley-config
source folder in the package org.spicefactory.parsley.tag. As you'll see most of them are fairly
simply, only delegating to the configuration DSL. You should always strive to reduce the logic in your tag
implementations, making it easier to apply the feature programmatically in cases where using metadata is not appropiate.
To give a simple example, lets assume that you have an application that dispatches NavigationEvents managed by
Parsley whenever the user navigates to a new tab or window. Instead of using the generic MessageHandler
tag for getting notified you could create a custom tag that applies only for these navigation events:
[NavigateTo("addUserForm")]
public function handleNavigation () : void
The implementation for this tag would look like this:
[Metadata(types="method", multiple="true")]
public class NavigateTo implements ObjectDefinitionDecorator {
[DefaultProperty]
public var target:String;
[Target]
public var method:String;
public var scope:String = ScopeName.GLOBAL;
public function decorate (builder:ObjectDefinitionBuilder) : void {
builder
.method(method)
.messageHandler()
.scope(scope)
.type(NavigationEvent)
.selector(target);
}
}
The [Metadata] tag is required to configure the metadata tag, it is explained in 12.2.5 Registering Metadata Tags.
The [DefaultProperty] tag marks a property that can be used without attribute name. Without that tag you'd need to always
explicitly specify the attribute name, so the example from above would read: [NavigateTo(target="addUserForm")].
The [Target] attribute tells the framework that this is the property that holds the name of the member of the class
(property or method). For metadata to be placed on the class level this is not required of course.
The
scope property allows to define handlers only interested in navigation events for a particular scope.
Finally we use these property values in registering a regular MessageHandler through the ObjectDefinitionBuilder.
We are passing the scope value, using the global scope as the default in case the attribute is omitted. The name of the navigation
target is used as the selector for the handler, while the message type (NavigationEvent) is hard-coded in this case.
In the simple example in the preceding section we merely applied an existing feature using the configuration DSL.
When you want to add custom functionality not provided out of the box, some more work is required. In many cases this
involves performing some configuration task on the target object once it gets instantiated, but before it is passed
to the application. For this purpose an ObjectProcessor can be used. This is the interface that needs
to be implemented:
public interface ObjectProcessor {
function preInit () : void;
function postDestroy () : void;
}
Again, fairly simple. The preInit method will be invoked after the object was instantiated, but before its
[Init] method will be invoked (in case it has one). Likewise the postDestroy method will be invoked
when the object was removed from the Context.
The 1:N Relationship between Decorators and Processors
Understanding this concept is important before implementing your first ObjectProcessor.
There is a 1:N relationship between a decorator and a processor. When you implement
the ObjectDefinitionDecorator tag like shown in preceding sections, it will be invoked once for a particular
definition in your Context. If it is a definition created by an <Object> tag or any other tag which represents a
singleton, then the decorator will also map to only one processor. But for other types of objects, like those declared
with <DynamicObject> or <DynamicCommand> multiple instances may be created from a single definition.
In this case one decorator maps to multiple processors, as a new processor gets created for each new instance.
This mechanism has the advantage that you can keep state that only
applies to a single target instance inside the processor. This greatly simplifies a lot of common tasks performed by processors
as most processors need to unregister some configuration artifact in the postDestroy method which they
created and applied in the preInit method.
This simplification is the major reason why version 2.3 introduced the distinction between decorators and processors
and the 1:N relationship between them. Before that many custom tags that had been built were broken as they did not
deal with the fact that one decorator may be applied to multiple instances, often ending up with patterns where
the configuration of the second object overwrote the internal state kept for the first object, thus introducing bugs
that were hard to track down by application developers.
Example: The ManagedEventsProcessor
Due to this 1:N relationship you also need a factory that creates a new processor for each new instance created for
a particular definition. That sounds more complicated than it actually is, so let's look at an example,
the implementation for the builtin [ManagedEvents]
tag, albeit somewhat simplified for the sake of brevity.
public class ManagedEventsProcessor implements ObjectProcessor {
private var target:IEventDispatcher;
private var names:Array;
private var dispatcher:Function;
function ManagedEventsProcessor (target:ManagedObject, names:Array, dispatcher:Function) {
this.target = IEventDispatcher(target.instance);
this.names = names;
this.dispatcher = dispatcher;
}
public function preInit () : void {
for each (var name:String in names) {
target.addEventListener(name, dispatcher);
}
}
public function postDestroy () : void {
for each (var name:String in names) {
target.removeEventListener(name, dispatcher);
}
}
public static function newFactory (names:Array,
dispatcher:Function) : ObjectProcessorFactory {
var params:Array = [names, dispatcher];
return ObjectProcessorFactories.newFactory(ManagedEventsProcessor, params);
}
}
The preInit and postDestroy methods simply loop through the event names and add the listeners
when the object gets created and remove them once the object gets removed from the Context.
Then there is a static
newFactory method that can be used to create a factory for this processor. This is a pattern that is not
required, but recommended, as it is quite straightforward and all the builtin processors use this pattern, too.
If you use the ObjectProcessorFactories utility function like above (again not required but convenient),
you need to know that it is based on a convention how the constructor of your processor has to look like.
The utility function expects the class of the processor it should create and any number of parameters to pass
to the constructor. But as you see the actual constructor has one additional parameter which is of type
ManagedEvent. The utility function will always pass the actual target instance as the first
parameter, followed but the ones you specified.
Finally, in the corresponding decorator, you need to add the processor to the definition:
[Metadata(types="class", multiple="true")]
public class ManagedEvents implements ObjectDefinitionDecorator {
[DefaultProperty]
public var names:Array;
public var scope:String;
public function decorate (builder:ObjectDefinitionBuilder) : void {
var sm:ScopeManager = builder.config.context.scopeManager;
var dispatcher:Function = new MessageDispatcher(sm, scope).dispatchMessage;
builder
.lifecycle()
.processorFactory(ManagedEventsProcessor.newFactory([event], dispatcher));
}
}
Here we use the static factory method we added to the processor. The framework will take care of the rest and make sure you get a fresh instance of your processor for each target to be configured.
Note that the example above differs from the real implementation of the decorator for managed events. The real tag simply uses the configuration DSL to specify the managed events, but that way we could not demonstrate how to directly connect a processor with a decorator.
Finally you have to tell the framework and the Flex compiler about your custom metadata.
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 18.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="method")]
public class NavigatorTo 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 method declarations. Other tags may be placed on properties or on the class level.
2. Add the class to the Spicelib Metadata Support:
Metadata.registerMetadataClass(NavigatorTo);
This must happen before you create your first Parsley Context.
3. Add the metadata tag to mxmlc or compc compiler arguments:
-keep-as3-metadata+=NavigatorTo
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.
For some general explanations on metadata configuration, like metadata inheritance or inconsistencies of the Flex compiler in dealing with metadata, see 3.1 Configuration with AS3 Metadata.
In many applications you may be able to stick to the builtin tags like <Object> or <DynamicCommand>
for your MXML or XML configuration files. But in case you want to add custom options the format is easy to extend.
Like with the metadata extension point, all the builtin tags like <Object> or <DynamicCommand> are implemented
as extensions themselves.
Although technically there are a lot of differences between MXML and XML configuration, they are very similar from the framework's perspective. This means that you usually only have to create one implementation of one of the available interfaces and will then be able to use the same tag in MXML and XML configuration.
The following example shows all the existing types of MXML and XML tags in a single configuration artefact.
This example shows an MXML configuration class, but apart from minor syntactical differences the structure for XML configuration files is exactly the same, and the same set of interfaces is supported.
As you see, you'll have two choices for tags on the root level: tags that implement RootConfigurationElement
or literal values. In case of literal values they are interpreted as an instance that should be added to the Context as is.
The class may have metadata tags for configuration which the framework will process, but the tag cannot have those
framework specific MXML child tags. In case of tags implementing RootConfigurationElement the framework will
invoke the process method. For details see 12.3.2 The RootConfigurationElement Interface and 12.3.4 Example: The MessageConfirmation Tag.
The NestedConfigurationElement shares some similarities with the interface for root tags. It can be used wherever
tags allow child tags that represent values, like the <Property>, <ConstructorArgs> or <Array> tags.
Like with root tags in any of these places literal values are allowed and are used as is, while for the tags implementing the
framework interface their resolve method will be invoked first to determine the actual value represented by that tag.
For details see 12.3.3 The NestedConfigurationElement Interface.
Finally there are places where tags implementing ObjectDefinitionDecorator can be used. Amongst other places they can
be used as immediate children of the root tags <Object>, <DynamicObject> and <DynamicCommand>.
This is the same interface that custom metadata tags have to implement. Thus a single implementation of that interface can be used
as metadata, MXML or XML, allowing for a great level of flexibility.
The reason that they are allowed immediately below tags like <Object> is that they contain logic to configure
an object, in the case of MXML or XML usage always the object defined by its parent tag. Custom metadata tags can support nested
decorator tags, too. For this they must have a DefaultProperty of type Array that expects elements of type ObjectDefinitionDecorator.
And of course the tag must contain logic that knows how to apply the decorators to the corresponding definition.
Most of the builtin metadata tags support MXML or XML configuration, too. Consider the following metadata tag:
[MessageHandler(scope="local")]
public function handleMessage (msg:MyMessage) : void
The equivalent XML configuration would be:
<object type="com.foo.MyClass">
<message-handler method="handleMessage" scope="local"/>
</object>
As you see the major difference is that you have to specify the method name in XML. This is not needed with metadata as the name will be deduced from the property the tag is placed upon.
This interface can be implemented by tags that are intended for use on the top level of MXML or XML configuration files.
public interface RootConfigurationElement {
function process (config:Configuration) : void;
}
In many cases the implementation of the process method creates, configures and registers a single definition
for an object. Many of the builtin tags like <Object> behave like that. But it is not a requirement. The tag
can alternatively register multiple object definitions or even none at all and instead do any other kind of processing.
The Configuration instance passed to the process method gives access to the configuration DSL.
For instruction on how to use the DSL see 12.4 Working with ObjectDefinitions.
This interface is similar to RootConfigurationElement, but comes with a subtle difference: In contrast to
the root tag which basically can perform any kind of processing, this tag has to represent exactly one value. The reason
is that such a tag is only used in places where a value is expected. For child tags of the <Array> tag for example
it is expected that each child tag represents an element of that Array. Therefor the interface is slightly different:
public interface NestedConfigurationElement {
function resolve (config:Configuration) : Object;
}
The resolve method must return a value. This is interpreted as the actual value the tag represents.
But there can be one more level of indirection: The returned value can represent something which needs to be resolved
at the time the value is applied to the instance it belongs to. This applies for the <ObjectRef> tag for
example. That tag has to resolve an id value or an object type at the time the target object is configured.
Such a return value must implement the ResolvableValue interface. For examples you may examine
the NestedObjectTag or ObjectReferenceTag classes.
One of the simplest tag implementations is the <MessageConfirmation> utility tag, so it serves
well as a quick example. The tag allows a message type and optional selector to be defined that should trigger
a confirmation dialog before proceeding with message processing. It can be used like this in MXML configuration
classes:
<MessageConfirmation
type="{DeleteUserMessage}"
scope="local"
title="Confirmation"
text="Do you really want to delete this user?"
/>
So whenever a message of type DeleteUserMessage is dispatched this utility will kick in first
and show the dialog using the provided text. When the user clicks cancel, message processing will be cancelled.
When she clicks Ok, processing of the message resumes, invoking any [MessageHandler] that was defined
for that message type.
This is how the implementation looks like:
public class MessageConfirmationTag implements RootConfigurationElement {
public var text:String;
public var title:String;
public var scope:String = ScopeName.GLOBAL;
public var type:Class;
public var selector:*;
public function process (config:Configuration) : void {
var builder:ObjectDefinitionBuilder
= config.builders.forClass(MessageConfirmation);
builder
.constructorArgs()
.value(title)
.value(text);
builder
.method("showAlert")
.messageInterceptor()
.type(type)
.selector(selector)
.scope(scope);
builder
.asSingleton()
.register();
}
}
It should be almost self-explanatory. It is a good example for how clean and simple the use of the configuration DSL
can make the implementation of such a tag. It merely uses the values of the properties set by the user to talk to the API
to define constructor arguments and a message receiver for the actual utility class. Note that the tag as such does not
contain the actual processing logic. This has been moved into the MessageConfirmation class that is configured
and registered by this tag. It is always good to keep configuration and runtime logic separate.
Creating a namespace for your custom MXML tags is not required. You can use them like any other class in MXML: map a tag prefix to the package of your tag and use it as is. But if you create a larger collection of reusable tags your users will probably be happy if you provide a single namespace that holds all the tags of the extension.
Creating such a namespace is a Flex SDK feature. Nothing is specific to Parsley in this respect, so you can simply look up the Flex documentation on that feature. But for the sake of completeness, we'll give a short summary of the necessary steps here.
First you need to create a manifest file, that list all the tags that should be included in the namespace.
If you checkout the Parsley project from SVN you can find the manifest files for all Parsley namespaces in the
manifest folder. This is an example for how such a file might look like:
<componentPackage>
<component id="Objects" class="com.foo.ObjectsTag"/>
<component id="Object" class="com.foo.ObjectTag"/>
<component id="View" class="com.foo.ViewTag"/>
<component id="DynamicObject" class="com.foo.DynamicObjectTag"/>
<component id="NestedObject" class="com.foo.NestedObjectTag"/>
<component id="DynamicCommand" class="com.foo.DynamicCommandTag"/>
</componentPackage>
It is a simple mapping of a tag name to the fully qualified class name. As you see the tag name does not have to be the same as the class name.
To compile such a namespace into your SWC the following compiler options need to be specified for compc:
-namespace http://www.myNamespaceUrl.com ${project.dir}/manifest/myNamespace-manifest.xml
-include-namespaces=http://www.myNamespaceUrl.com
In contrast to MXML creating a namespace for custom tags is required for XML. It's the only way to expand the basic feature set of the Parsley XML tags. The XML support is built on top of the Spicelib XML-Object-Mapper. For full documentation see 19 XML to Object Mapper. But often the full feature set is not required. For basic tags that can also be used in MXML they often only utilize attributes for configuration and avoid any complex structure. Since mapping to attributes is the default behaviour of the XML-Object-Mapper, nothing has to be configured explicitly then. You'd only need to create the namespace and list the classes that should belong to that namespace:
XmlConfigurationNamespaceRegistry
.getNamespace("http://www.myNamespaceUrl.com")
.mappedClasses(SomeTag, SomeOtherTag, YetAnotherTag);
You can then use the new namespace alongside the builtin Parsley namespace:
<objects xmlns="http://www.spicefactory.org/parsley"
xmlns:myNS="http://www.myNamespaceUrl.com">
<object type="com.foo.NormalObject"/>
<myNS:some-tag />
<myNS:yet-another-tag />
</objects>
For the mechanism and supported interfaces see 12.3.1 The Structure of Configuration Files as it is the same as for MXML configuration classes.
In the proceeding sections you were introduced to the mechanisms of creating custom tags, either as metadata, MXML or XML.
With all of these types of tags you often need to create an ObjectDefinition or modify and existing one.
This can be done with the help of the configuration DSL. This section lists the most important options. For further
details you may want to browse the ASDoc for the DSL.
This is usually only required in MXML or XML tags as metadata tags is most often used to configure existing definitions.
If you implement RootConfigurationElement or NestedConfigurationElement for a custom MXML or XML tag,
then the provided Configuration instance can be used to obtain a new builder for an ObjectDefinition:
public function process (config:Configuration) : void {
var builder:ObjectDefinitionBuilder = config.builders.forClass(MessageConfirmation);
[...]
}
Things you can do with such a builder will be explained in the following sections.
When implementing ObjectDefinitionDecorator for a custom metadata tag, a builder has already
been created and will be passed to the decorator:
public function decorate (builder:ObjectDefinitionBuilder) : void {
In rare cases a metadata tag may also wish to create and register additional definitions.
The Configuration instance for the builder is accessible through builder.config
and can be used like in the process method above.
You can define literal values or injections.
Literal Values
builder
.property("firstName")
.value("Ann");
Injection by Type
If the type can be deduced from reflecting on the property type:
builder
.property("service")
.injectByType();
If the type cannot be determined through reflection, it can be specified explicitly:
builder
.property("service")
.injectByType(LoginService);
Injection by Id
builder
.property("service")
.injectById("loginService");
This is equivalent to using [Inject(id="loginService")] on the property declaration.
Like for properties literal values and injections can be specified.
Literal Values
builder
.constructorArgs()
.value("Alison")
.value("Goldfrapp");
Injection by Type
If the type for all parameters can be deduced from reflecting on the parameters of the constructor:
builder
.constructorArgs()
.injectAllByType();
Note that there is a bug in current Flash Players that reports '*' as the type for constructor arguments
instead of the actual declared type if you reflect on a class before any instances of that class have been created.
If the types cannot be determined through reflection, it can be specified explicitly:
builder
.constructorArgs()
.injectByType(LoginService);
.injectByType(CartService);
.injectByType(ProductService);
Injection by Id
builder
.constructorArgs()
.injectById("loginService");
Of course any mechanisms can be mixed:
builder
.constructorArgs()
.injectById("loginService")
.value("http://www.someDomain.com/");
If you want to apply one of the builtin message receivers like [MessageHandler]
or [CommandResult] programmatically, they are all available through the DSL:
builder
.method("intercept")
.messageInterceptor()
.type(LoginMessage)
.scope("local");
builder
.method("result")
.commandResult()
.type(SaveUserMessage)
.scope("window");
builder
.property("status")
.commandStatus()
.type(LoadConfigMessage)
.selector("loadServices");
In cases where you created a custom message receiver by directly implementing one of the core
receiver interfaces like MessageTarget or CommandObserver or extending one of the builtin
receivers, you can create a custom factory for these receivers:
builder
.lifecycle()
.messageReceiverFactory(new MyCustomFactory());
This is the interface such a custom factory must implement:
public interface MessageReceiverFactory {
function createReceiver (provider:ObjectProvider) : MessageReceiver;
}
This factory will be invoked for each instance that gets created from the target definition. Most implementations will very likely be a one liner, taking the specified provider for the new instance and using it as an argument for the constructor of the returned receiver instance.
The framework will do the rest for you. Like registering that receiver when the object gets created and removing it again when the object gets removed from the Context. It also takes care of the complex proxy handling for singleton definitions. In those cases a receiver proxy will be registered even before the target instance will be created, to avoid scenarios where an instance "misses" a message just because it was created after the sender at container startup.
This is a hook that allows you to define the way how the target instance gets instantiated. In this case any constructor arguments that were already specified will be ignored since it is this instantiator who is responsible for creating the object then. This is how an instantiator can be specified:
builder
.lifecycle()
.instantiator(new MyInstantiator());
And this is the interface that must be implemented:
public interface ObjectInstantiator {
function instantiate (target:ManagedObject) : Object;
}
It simply has to return the new instance that should be added to the Context. The specified target
parameter gives access to the ObjectDefinition and the associated Context. The property target.instance
is null still at this point. Obviously, because this will become the instance that this method returns.
This is a hook for performing configuration logic when the object gets created and when it gets removed. This is explained in detail in section 12.2.4 The ObjectProcessor Interface.
Do not use such a processor to register and unregister message receivers. It would work in many cases, but would not be as robust as using a factory for a message receiver as demonstrated in 12.4.5 Custom Message Receivers.
Finally after all configuration options have been specified like shown in preceding sections,
the object has to be registered. Registering a definition has the same effect as adding
an <Object> tag to an MXML configuration class: the object represented by the definition becomes
available for dependency injection or may be fetched from the Context directly, e.g. with Context.getObject().
There are two types of objects you can register: Singletons, which will be created on container startup and will remain managed until the Context gets destroyed. In this case only one instance will be created from the definition. The second option is a DynamicObject. This can be created and removed from the Context at any point in time. And more than just one instance can be created from such a definition. Depending on what type of definition you create there are some final options for the registration available:
Singletons
builder
.asSingleton()
.id("someId")
.lazy(true)
.order(1)
.register();
All the options above are optional, to simply apply the defaults (non-lazy singleton with no particular order) you can reduce the above to:
builder
.asSingleton()
.register();
Dynamic Objects
builder
.asDynamicObject()
.id("someId")
.register();
Again the id is optional and may be omitted. Lazy or order options do not apply to DynamicObjects.
Sometimes you want to create an object that is not registered and available as a root definition, but only gets used within another definition as a kind of private dependency. Something equivalent to the following MXML configuration:
<Object type="{SomeService}">
<ConstructorArgs>
<NestedObject type="{SomeCollaborator}"/>
</ConstructorArgss>
</Object>
In this case the instance of SomeCollaborator is not available for injection or for being fetched directly
from the Context instance. Instead it only gets injected as a dependency into the constructor of the host object,
with the lifecycle of the dependency being tied to that of the host.
If you want to do the same in code, this is how it works:
var config:Configuration = ...;
var dependency:DynamicObjectDefinition = config.builders
.forClass(SomeCollaborator)
.asDynamicObject()
.build();
var builder:ObjectDefinitionBuilder = config.builders
.forClass(SomeService);
builder
.constructorArgs()
.injectFromDefinition(dependency);
builder
.asSingleton()
.register();
As you see, we do not call register for the first definition we create. We just call build instead,
which gives us a definition the container does not know about. We then pass it to injectFromDefinition for the
constructor arguments of the other definition and only register the latter with the container.
When reflecting on a class where there is already an existing builder for, you can simply use the ClassInfo
instance avaibale through that builder:
var p:Property = builder.typeInfo.getProperty("service");
If for some reason you have to create a ClassInfo instance yourself, remember to always pass the ApplicationDomain that the Context your tag lives in belongs to:
var config:Configuration = ...;
var ci:ClassInfo = ClassInfo.forClass(SomeClass, config.domain);
Without passing the domain the tag may not work properly in modules loaded into child ApplicationDomains.
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. And often it would be beneficial if there would be a way to avoid just having one global manager instance.
If you want to allow the users of your custom extension tags to specify scopes the same way like for messaging,
so the feature can be applied globally or just locally for one Context only or for a any custom scope.
This can be accomplished by registering a scope extension.
A core framework feature that is built on top of a ScopeExtension is the support for Decoupled Bindings with
the [Publish] and [Subscribe] tags. If you want to browse the code for inspiration on building
scope-wide services, you find the implementation in the parsley-extensions source folder in the package
org.spicefactory.parsley.binding.
Through the use of scopes a publisher may publish an object to a local scope only, which allows for multiple windows, popups or modules to have their own local publishing space without creating ambuigities through multiple publisher declarations.
For a custom scope-wide service you first 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. So in most cases it is recommended to use this mechanism to have a distinct manager instance for each individual scope.
A tag implementation may then fetch the manager instance that belongs to the specified target scope:
public class EnterScreenDecorator implements ObjectDefinitionDecorator {
public var scope:String = ScopeName.GLOBAL;
[...]
public function decorate (builder:ObjectDefinitionBuilder) : void {
var scope:Scope = builder.config.context.scopeManager.getScope(scope);
var navigationManager:NavigationManager
= scope.extensions.byType(NavigationManager) as NavigationManager;
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 can also use
the scope-wide manager in an ObjectProcessor and work with the actual target instance instead of just
the definition.
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";
Configurations
.forRegistry(registry)
.builders
.forClass(type)
.asSingleton()
.id(id)
.register();
The configuration DSL of the framework always processes metadata on the configured classes and combines the metadata configuration with the options specified programmatically through the DSL.
Your custom processor can then be combined with any existing builder:
MXML:
<parsley:ContextBuilder>
<parsley:FlexConfig type="{BookStoreConfig}"/>
<parsley:XmlConfig file="logging.xml"/>
<parsley:CustomConfig>
<mynamespace:MyCustomConfigurationProcessor/>
</parsley:CustomConfig>
</parsley:ContextBuilder>
ActionScript DSL:
var viewRoot:DisplayObject = ...;
ContextBuilder.newSetup()
.viewRoot(viewRoot)
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.config(XmlConfig.forFile("logging.xml"))
.config(new MyCustomConfigurationProcessor())
.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 Processors
If your processor 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 processor 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 succeeded or failed.
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 with the ContextBuilder DSL:
var viewRoot:DisplayObject = ...;
ContextBuilder.newSetup()
.viewRoot(viewRoot)
.factories().viewManager(new MyCustomViewManagerFactory())
.newBuilder()
.config(FlexConfig.forClass(BookStoreConfig))
.build();
For reusable replacements to be used in Flex applications it is recommended to create a simple
custom tag like shown in 12.9 Initializing Extension Modules that can then be used as a child tag in a ContextBuilder
tag.
Decorating IOC Kernel Services
For most scenarios it is recommended not to create a replacement from scratch but instead decorate an existing service. There are two reasons why you should prefer a decorator. First, in most cases you want to add or modify a certain aspect of a core service and not implement it with a totally different behavior. Second, there might be 3rd party frameworks or extensions that in turn want to replace or decorate a core service, which would fail if those were full replacements, as one replacement would overwrite the other. With a decorated service there is no limitation on the number of decorators you wrap around the original service.
This is how you would write a very simple decorator that simply logs whenever a view root is added or removed from the Context. (This is a contrived example as Parsley does that anyway, it's just to demonstrate the principle.)
package {
// [imports...]
public class LoggingViewManagerFactory implements ViewManagerFactory {
private var delegateFactory:ViewManagerFactory;
function LoggingViewManagerFactory (delegate:ViewManagerFactory) {
this.delegateFactory = delegate;
}
public function create (context:Context, domain:ApplicationDomain,
settings:ViewSettings) : ViewManager {
var delegate:ViewManager = delegateFactory.create(context, domain, settings);
return new LoggingViewManager(delegate);
}
/* deprecated methods omitted, in Parsley 2.x they still must be implemented */
}
}
// [imports...]
class LoggingViewManager implements ViewManager {
private static const log:ILogger = Log.getLogger("com.foo.LoggingViewManager");
private var delegate:ViewManager;
function LoggingViewManager (delegate:ViewManager) {
this.delegate = delegate;
}
public function addViewRoot (view:DisplayObject) : void {
log.info("Adding view root " + view.name);
delegate.addViewRoot(view);
}
public function removeViewRoot (view:DisplayObject) : void {
log.info("Removing view root " + view.name);
delegate.removeViewRoot(view);
}
}
As you can see we are simply wrapping the ViewManager and the ViewManagerFactory.
You can then apply this factory globally like this:
GlobalFactoryRegistry.instance.viewManager
= new LoggingViewManagerFactory(GlobalFactoryRegistry.instance.viewManager);
The next section gives you an overview over all services that can be replaced or decorated.
List of IOC Kernel Services
CompositeContextBuilder | Responsible for processing the configuration
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.
For a general description of the command support, see 6.9 Asynchronous Command Methods and
6.10 Short-lived Command Objects. The builtin command types are those that support command methods
that return AsyncToken or Task from the Spicelib Task Framework. If you want to see how
these are implemented, browse the code for AsyncTokenCommandFactory and TaskCommandFactory.
The command type is detected by the return type declared for the command method, any return type other than the
two listed above must be supported explicitly through a new CommandFactory. Such a factory creates new
command instances based on the return value of the original command method passed to the factory.
Most of the complexity of handling commands, like distributing the result to all the [CommandResult] handlers
with a matching parameter type, are generified and available for all existing and new command types. The CommandFactory
primarily extracts the (surprisingly few) aspects that are specific for each command type.
We'll show a simple example here for a custom command that understands how to deal with an URLLoader.
package example {
import org.spicefactory.parsley.core.messaging.command.Command;
import org.spicefactory.parsley.core.messaging.command.CommandFactory;
import flash.net.URLLoader;
public class URLLoaderCommandFactory implements CommandFactory {
public function createCommand (returnValue:Object,
message:Object, selector:* = undefined) : Command {
return new URLLoaderCommand(URLLoader(returnValue), message, selector);
}
}
}
import org.spicefactory.lib.reflect.ClassInfo;
import org.spicefactory.parsley.core.messaging.command.impl.AbstractCommand;
import flash.events.*;
import flash.net.URLLoader;
class URLLoaderCommand extends AbstractCommand {
private var loader:URLLoader;
function URLLoaderCommand (loader:URLLoader, message:Object, selector:*) {
super(loader, message, selector);
this.loader = loader;
loader.addEventListener(Event.COMPLETE, loaderComplete);
loader.addEventListener(IOErrorEvent.IO_ERROR, loaderError);
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loaderError);
start();
}
private function loaderComplete (event:Event) : void {
removeEventListeners(event.target as IEventDispatcher);
complete(event);
}
private function loaderError (event:ErrorEvent) : void {
removeEventListeners(event.target as IEventDispatcher);
error(event);
}
private function removeEventListeners (task:IEventDispatcher) : void {
task.removeEventListener(Event.COMPLETE, loaderComplete);
task.removeEventListener(IOErrorEvent.IO_ERROR, loaderError);
task.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loaderError);
}
protected override function selectResultValue (result:*, targetType:ClassInfo) : * {
if (targetType.isType(Event)) {
return result;
}
var loader:URLLoader = Event(result).target as URLLoader;
if (targetType.isType(URLLoader)) {
return loader;
}
return loader.data;
}
protected override function selectErrorValue (result:*, targetType:ClassInfo) : * {
return (!targetType.isType(Event))
? Event(result).target
: result;
}
}
As you see the factory itself is fairly simple. It just creates a new instance of URLLoaderCommand, a private class.
The command itself extends AbstractCommand which contains a lot of the plumbing every command type needs.
Apart from that it basically performs two tasks:
First it adds all the necessary listeners to detect that the loading process has either succeeded or failed and then
invokes the complete or error methods respectively. It passes the event as the result value in both cases.
This is not required, you can pass any kind of value to these functions. But it allows for some flexibility in what result
types are supported in result handlers.
The second task it performs is overriding selectResultValue and selectErrorValue. After a command signals
that it is complete (through invoking complete or error), the framework will invoke the corresponding select
method for each matching result handler. It passes the targetType info for each handler into the select method. This
is the declared parameter type of the handler method. The custom command can then use this type information to decide what to provide
as the result value.
In our example we allow the result handler to either accept the event or the URLLoader instances or the actual
data loaded by that loader. So the following signatures would all be valid for a result handler:
[CommandResult]
public function handleResult (event:Event, msg:LoadConfigMessage) : void
[CommandResult]
public function handleResult (loader:URLLoader, msg:LoadConfigMessage) : void
[CommandResult]
public function handleResult (fileContent:String, msg:LoadConfigMessage) : void
The type of the first parameter is the typeInfo passed to the select methods.
Note that we do not throw an Error, even though we would be able to find out if the target type matches.
If the type is neither Event nor URLLoader we blindly pass loader.data.
This is because that through the Converter services of the Spicelib Reflection API which is used to
finally invoke the target method, there might be a converter registered for the target type if it does
not match. Therefor it is recommended not to fail-fast in those methods. If the type does not match and there
is no converter, the application will run into a runtime error anyway.
Finally a custom CommandFactory must be registered with the framework:
var settings:MessageSettings = GlobalFactoryRegistry.instance.messageSettings;
settings.commandFactories.addCommandFactory(URLLoader, new URLLoaderCommandFactory());
We pass the supported return type and the matching command factory. It is recommended to offer
this setup logic in a simple custom tag that can be placed within <ContextBuilder> tags
like demonstrated in 12.9 Initializing Extension Modules.
After the factory is registered, command methods and command objects that return an URLLoader are supported:
[Command]
public function execute (msg:LoadConfigMessage) : URLLoader {
var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.TEXT;
loader.load(new URLRequest(file));
return loader;
Alternative Implementation as a Task
Instead of creating a new command type, you may sometimes prefer to think about a custom Task implementation.
The advantage would be that such a Task can then even be used without Parsley and outside of commands. It could
be used in a TaskGroup for example that executes multiple asynchronous operations in sequence or in parallel.
If such a URLLoaderTask would extend ResultTask instead of the base Task interface
the result handling in commands would be almost the same, as Parsley already knows and supports ResultTasks
out of the box.
When you create a set of configuration tags and possibly some scope-wide managers that integrate with them, you may want to offer your users a convenient way to activate that extension:
<parsley:ContextBuilder config="{MyConfig}">
<app:NavigationSupport/>
</parsley:ContextBuilder>
Here a custom MXML tag as a child of the <ContextBuilder> tag takes the role of doing all the necessary
setup to make the extension available for that Context.
Child tags of <ContextBuilder have to implement the ContextBuilderProcessor interface:
public class NavigationSupportTag implements ContextBuilderProcessor {
private static var initialized:Boolean = false;
public static function initialize () : void {
if (initialized) return;
Metadata.registerMetadataClass(EnterScreenDecorator);
Metadata.registerMetadataClass(ExitScreenDecorator);
var factory:ScopeExtensionFactory = new NavigationManagerFactory();
GlobalFactoryRegistry.instance.scopeExtensions.addExtension(factory);
[...]
initialized = true;
}
public function processBuilder (builder:CompositeContextBuilder) : void {
initialize();
}
}
The processBuilder method of the implemented interface will simply delegate to a public static method.
This has the advantage that you can use the same class programmatically, in cases where you don't use the MXML tags
to declare the Context:
NavigationSupport.initialize();
Like shown in our example, most common tasks in such an initializer are registering custom metadata tags or scope-wide managers.
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 class. 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.
<parsley:ContextBuilder config="{MyConfig}">
<app:NavigationSupport/>
<app:NavigationXmlSupport/>
</parsley:ContextBuilder>
In the example above XML support is activated explicitly in addition to the core extension module. The XML initializer is responsible for configuring the custom XML configuration namespace. For details see 12.3.6 Creating Custom XML Namespaces.