Previously I discuss a simple mechanism in
Conduit
to enable you to apply data transformations on outgoing data from
ColdFusion to Flex, using a simple inheritance strategy, to overwrite
how the CFC=>Flex VO object translation occurred.
The only issue there is, if you need to be able to do different
transformations, you end up having to combine separate code-bases into
a single
CFDeserialiser or CFSerialiser , which is not very portable, or decoupled.
To solve this problem, Conduit has
filters that can be
applied at different points in the AMF communication life cycle, and
manipulate the data that is being passed through.
So, if we take the example we looked at previously, where we reversed
every simple value that was travelling from ColdFusion to Flex, we can
convert this into a simple filter, which is much easier to apply, and
can be combined with other filters to do fairly complex data
manipulation.
An example ReverseFilter looks like this:
<cfcomponent hint="filter that reverses all strings" output="false">
<cffunction name="init" hint="Constructor" access="public" returntype="ReverseFilter" output="false">
<cfargument name="args" hint="the argument ConfigMap" type="struct" required="Yes">
<cfscript>
return this;
</cfscript>
</cffunction>
<cffunction name="doFilter" hint="does the filtering - reverses strings" access="public" returntype="void" output="false">
<cfargument name="filterData" hint="the data for the filter" type="struct" required="Yes">
<cfargument name="filterChain" hint="the filter chain" type="conduit.core.filter.FilterChain" required="Yes">
<cfscript>
>//we know there will be a 'result', in the after filter
if(isSimplevalue(arguments.filterData.result))
{
arguments.filterData.result = Reverse(arguments.filterData.result);
}
>//push on to the next filter
arguments.filterChain.next();
</cfscript>
</cffunction>
</cfcomponent>
The doFilter() method is the method that manipulates the incoming data,
in this case, looking at the 'result' argument that is being passed in,
and if it's a simple value, reversing it.
The Filter is then passed on to any subsequent filters, by calling the
next() method on the filterChain. If we didn't want filtering to
continue, we could simply skip that step.
We then configure this ReverseFilter against the CFSerialiser, to convert outgoing data that is going to Flex, like so:
<destination id="Conduit">
<channels>
<channel ref="my-cfamf" />
</channels>
<adapter ref="conduit" />
<properties>
<source>*</source>
<cfcs>
<!--- cfc config --->
</cfcs>
<filters>
<!-- This is an example filter that reverses strings -->
<filter>
<path>conduit.core.filter.example.ReverseFilter</path>
<apply-after>serialiser</apply-after>
</filter>
</filters>
</properties>
</destination>
The 'apply-after' section, tells Conduit to fire the ReverseFilter
after the processing of the CFSerialiser, rather than before, although
in this case, it won't make that much difference.
This is just a simple example for the sort of things that can be done
with Conduit Filters, but it should give you a good idea on how with a
few simple filters, you can drastically change the way data is sent to
and from Flex and ColdFusion.
If you want to read more about Conduit, please have a look at the
wiki, and daily builds can be downloaded from the
google group.
I'm a little late in posting this, but I had figured I should at least
mention that I am speaking at
WebDU this year again.
For those who haven't been before, WebDU
is a fantastic Adobe/Web technology focused conference that has been
happened in Sydney for quite a while now. This year I'm particularly
excited by a lot of the Flex/RIA content that is being presented.
I'll be there this year talking about a
Conduit , the ColdFusion adapter for Blaze DS,
which allows you to all sorts of weird and wacky things with data
transfer to and from Flex and ColdFusion. I'll be showing off some
cute and clever data transformations and interesting techniques for
data exposure using Conduit, so it should be a interesting presentation
for anyone doing ColdFusion and Flex development.
Look forward to seeing you all at WebDU!
Since I've been working with
Conduit,
there have been several 'enhancements' to the Flex Remoting that I've
implemented, above and beyond what ColdFusion provides out of the box.
These are mostly little things, but since Conduit is Open Source, we
are able to incorporate them into how Conduit works, and give us some
new tools to use when we set up Remoting.
One of the first enhancements I made, was around the fact that every time Flex invokes a CFC through Remoting,
it gets created on every request. Quite often, this is simply not
necessary, as the CFC in question has no state, or maybe you
want the CFC to maintain state, but you have to jump through some hoops to get that to happen.
In Conduit, you can now set a:
<cfcomponent remoteScope="application" >...</cfcomponent>
...and the CFC will be stored inside the specified shared scope.
For example, here is a CFC that gets stored in the session scope
when invoked, and keeps a simple incrementing number each time its
remote method gets called:
<cfcomponent hint="proxy stored in a share scope" output="false" remoteScope="session">
<cfscript>
instance.value = 1;
</cfscript>
<cffunction name="getIncrementingValue" hint="returns a value, and increments it by 1" access="remote" returntype="numeric" output="false">
<cfreturn instance.value++ />
</cffunction>
</cfcomponent>
So you can see here how we are able to maintain state between calls, within a single CFC.
The other enhancements I made, revolve around passing null values back and forth from AS3 Objects, and ColdFusion.
As we all know, there is no
null as such in ColdFusion, which can make dealing with null values from Flex slightly problematic.
I've added two new enhancements to <cfproperty> for Value Objects to help with this issue -
nullvalue and
nullmethod.
nullvalue provides a value for a given property to be used when
a null value is found in a Flex AS3 object coming down to ColdFusion.
This is particularly applicable when using getter/setters for your
properties on your ColdFusion Value Objects.
For example:
<cfcomponent output="false" alias="tests.cfml.cfc.model.Basic">
<cfproperty name="date" type="date" nullvalue="1-1-1900">
<cffunction name="getDate" access="public" returntype="date" output="false">
<cfreturn instance.date />
</cffunction>
<cffunction name="setDate" access="public" returntype="void" output="false">
<cfargument name="date" type="date" required="true">
<cfset instance.date = arguments.date />
</cffunction>
</cfcomponent>
...will set 'Date' to the 1-1-1900 if the AS3 Object's 'date' property
is null. Also, if sending data back up to Flex from CF, the 'date'
property is set to null on the Flex side, if the date value is
1-1-1900. This gives you a type safe way of dealing with null values
coming down from Flex.
The
nullMethod option for dealing with null values becomes more useful when dealing with object composition in Value Objects.
The
nullmethod attribute specifies a method on the CFC to be fired, when encountering a null value for the specified method, e.g.
<cfcomponent output="false" alias="tests.cfml.cfc.model.Basic">
<cfproperty name="simple" type="tests.cfml.cfc.model.Simple" nullmethod="removeSimple">
<cffunction name="setSimple" access="public" returntype="void" output="false">
<cfargument name="Simple" type="Simple" required="true">
<cfset instance.Simple = arguments.Simple />
</cffunction>
<cffunction name="removeSimple" hint="remove simple" access="public" returntype="void" output="false">
<cfset StructDelete(instance, "Simple") />
</cffunction>
</cfcomponent>
So in this case, whenever the property 'Simple' comes back from Flex as null, the method 'removeSimple' is called instead of attempting to set the property.
Now you can start to see what we can do with Conduit, since we can control all aspects of the Remoting process. So the question is - if you could change any aspect of ColdFusion <=> Flex Remoting, what would it be? Let me know, and we can see if we can incorporate it into Conduit!
If you want to read more, check out
Differences between Conduit and ColdFusion remoting, in the Conduit documentation.
This project started with writing the
translation layer for doing Transfer to Flex communication, I ran into a
pretty nasty bug in ColdFusion's remoting implementation,
and then ended up in a place that solves all these problems, and is
incredibly flexible and powerful to boot! Quite the round trip, but
well worth it in the end.
Conduit requires that you set up
BlazeDS
on your machine, as well as install the conduit.jar file, and some CFCs
as well. I'm not going to go into too much details here, as you can
read about in the
Library Installation section of the documentation.
To configure Conduit, we open up our remoting-config.xml, and we add the Conduit adapter to the <adapters> section:
<adapter-definition id="conduit" class="com.compoundtheory.conduit.adapters.ColdFusionAdapter"/>
And now we continue by adding a new
Destination to our remoting-config.xml, which we will then call from
Flex via RemoteObject (examples of the configuration are provided in the downloads).
<destination id="Conduit">
<channels>
<channel ref="my-cfamf" />
</channels>
<adapter ref="conduit" />
<properties>
<source>*</source>
<cfcs>
<!--
Whether or not reload the CFCs below on each request.
Useful for debugging when building new invokers,serialisers
or deserialisers
-->
<reloadcfcs>false</reloadcfcs>
<!--
The CFC that invokes the remote method call
-->
<invoker>conduit.core.CFCInvoker</invoker>
<!--
Translates CF=>AS3
-->
<serialiser>conduit.core.CFSerialiser</serialiser>
<!--
Translates AS3=>CF
-->
<deserialiser>conduit.core.CFDeserialiser</deserialiser>
</cfcs>
</properties>
</destination>
The id of the destination can be whatever you like, but we started with
'Conduit', so we would know what we are calling from Flex.
The <adapter> is specified to use the conduit adapter, rather than the default cf-object adapter.
We then set a series of properties, most important of which are setup
within the <cfc> section. This section controls what CFCs are
called upon to perform various duties within the ColdFusion <=>
Flex communication process.
Just to emphasise this point - this means that the majority of the heavy lifting done by Conduit is done
with ColdFusion code.
This makes it really easy to extend, change, manipulate or debug. It
gives you almost complete control over the AS3<=>CF translation
process, without you having to know much about Java at all (I will
admit there are some Java classes involved).
We can see from there are three CFCs that the Conduit adapter has configured for it.
The <invoker>
This is the CFC that does the actual method calling. In the instance
of the conduit.core.CFCInvoker that comes with Conduit, all it does is
create an instance of the CFC that has been requested in the
<RemoteObject>'s
source attribute, and calls the passed with method name and any parameters that were passed down from Flex.
The code looks something like this (just to show you how simple it is):
<cffunction name="execute" hint="Creates a cfc, and invokes a methodon it" access="public" returntype="any" output="false">
<cfargument name="source" hint="the cfc source" type="string" required="Yes">
<cfargument name="methodName" hint="the name of the method" type="string" required="Yes">
<cfargument name="params" hint="the parameters to pass in" type="any" required="Yes">
<cfscript>
var local = {};
var object = createObject("component", "#arguments.source#");
</cfscript>
<cfinvoke component="#object#" method="#arguments.methodName#"argumentcollection="#arguments.params#"returnvariable="local.return">
<cfif StructKeyExists(local, "return")>
<cfreturn local.return />
</cfif>
</cffunction>
Pretty straight forward, no?
The <serialiser>
The job of this CFC is to take whatever is returned from the
<invoker> and translate it into whatever you want to return back
to Flex. Since BlazeDS handles the AMF conversion part of it for us,
the conduit.core.CFSerialiser only really needs to convert CFCs in
Actionscript objects, set all their properties correct, and we are good to go.
The <deserialiser>
The deserialiser CFC's job it to take the incoming parameters that come
down from a Flex RemoteObject call, and translate them into something
usable for the <serialiser> CFC.
Again, since BlazeDS does a lot of the heavy lifting, the
conduit.core.CFDeseriailser's main job, is to convert Actionscript
Objects into their appropriate CFCs, and set all their properties
correctly.
Changing how things work
While there are plans to put some interesting enhancements for Conduit
to provide above and beyond what ColdFusion remoting already does, the
real power of Conduit comes from being able to write your own custom
Invoker, Serialiser or Deserialiser.
Say for example, when we send information from ColdFusion to Flex, we
want to reverse every Simple value (String, date, numeric) that we come
across. Don't ask me
why you would want to do this ;o), maybe you just like messing with your co-workers.
First thing we need to do is write our own custom CFSerialiser.cfc. For this example, I'm going to reuse the
conduit folder I would have setup, which already has a /conduit ColdFusion mapping pointing to it, and create a new folder called
reverse inside it.
So I create a new component, under /conduit/reverse, and call in
CFReverseSerialiser.cfc, and make it extend conduit.core.CFSerialiser.
We will now overwrite the
translate method, which handles what data gets converted, and how, depending on its data type.
The code would look something like this:
<cfcomponent output="false" extends="conduit.core.CFSeriaiser" hint="Component for serialising CF=>AS3, in reverse">
<cffunction name="translate" hint="translation function for objects,reverses simple values" access="private" returntype="any"output="false">
<cfargument name="object" hint="the object to serialise" type="any" required="no">
<cfargument name="cache" hint="local reference cache for cyclic graphs" type="any" required="Yes">
<cfscript>
if(isSimpleValue(arguments.object)
{
>//if it's simple, then reverse it!
return reverse(arguments.object);
}
else
{
return super.translate(argumentCollection=arguments);
}
</cfscript>
</cffunction>
</cfcomponent>
And we can configure a special 'ConduitReverse' destination for anyone who wants reversed Strings in their code:
<destination id="ConduitReverse">
<channels>
<channel ref="my-cfamf" />
</channels>
<adapter ref="conduit" />
<properties>
<source>*</source>
<cfcs>
<reloadcfcs>false</reloadcfcs>
<invoker>conduit.core.CFCInvoker</invoker>
<serialiser>conduit.reverse.CFReverseSerialiser</serialiser>
<deserialiser>conduit.core.CFDeserialiser</deserialiser>
</cfcs>
</properties>
</destination>
As you can see, we can do almost anything we want to the communication
process, simply by extending the core components (or even writing brand
new ones) and because its at a low level, its almost completely
seamless to those who are writing the ColdFusion and/or Flex code. That
being said, its open source, so we can add extra logging and/or
debugging as we need. No more failing silently!
Trying it out
This is still Alpha code, and there is a
lot of logging
currently in it, but there is enough there for you to start playing.
Code is in SVN, and daily builds are available from the
Google Group (saves you having to compile the .jar file). The
documentation is slowly getting fleshed out, but there is enough there to get started.
I am more than happy to get
code contributions, and/or ideas for how the to expand on the current ColdFusion remoting feature so send through whatever you have.
I hope you enjoy
Conduit!