Check out our Angular Book Series.

Learning Cairngorm (Part 3)

In the last part of this series, I spoke about how I was going to see if I could get the Cairngorm store working from ColdFusion instead of Java. I was successful. The exercise, unfortunately, did not move along my understanding of Cairngorm. However, it did teach me a lot about ColdFusion and Flex interrelate. There will come a day where I'm glad for this experience.

Turning Java Classes into ColdFusion Components

The first thing I did was to convert all the java classes into ColdFusion components. The java components are in "WEB-INF\com\adobe\cairngorm\samples\store\*".

I decided, for simplicity, to create my CFCs in the root directory of the Cairngorm store app, instead of in a subdirectory structure. You do not have to do that to use ColdFusion with Flex.

The first class I migrated was CreditCardDelegate. This contains a single function, validateCreditCard. It doesn't look like an credit card validation is done in this method, it just picks a random number. Half the time it will be validated, and half the time it won't. It's not real world, but for proof of principle purposes it suffices. The java class used Math.random to pick the random number. I used ColdFusion's function RandRange.

The next file was the ProductDelegate. It seems to me that a delegate is akin to service objects used commonly by ColdFusion developers. The ProductDelegate has a single method, getproducts. It returns a list type, which is a bit problematic. The Java List is an interface, with other types mapping to it. In the Data Access Object, the Linked List class is used. ColdFusion doesn't have a parallel. At first I was going to use a query, but eventually decided upon using an Array of value objects. The Java version of ProductDelegate does not create an instance of the ProductDAO inside it. This is something I changed in the CFC, although I'm not sure if it was an improvement, but I don't want to go back and change it.

The Java version had an abstract class named DAO. I didn't bother to convert this.

ColdFusion and Java Differences

Next there is a ProductDAO.java file. This is the data access object layer. The Java version creates a LinkedList of product value objects. The ColdFusion version creates an Array of product value objects. In the Java code, it looked like an instance variable was created (products) and then never used again. This was a result of my inexperience with Java. In ColdFusion, there is no distinction between variable definition and variable assignment. The variable is defined as a private instance variable:


private List products = new LinkedList();

And then later reinitialized in a method:


products = new LinkedList();

I mistook the second for creating an function local variable of the same name as the instance variable. It's not doing that, though. It is just re-initting it. The DAO does not access a database, local storage, an XML file, or web service to get it's data. It's all hard coded into the component. You wouldn't want to do this in the real world, but for the proof-of-principle-ness of this app, it fine.

I took each Java code block, like this:


ProductVO product1 = new ProductVO();
product1.setId( 1 );
product1.setName( "USB Watch" );
product1.setDescription( "So, you need to tell the time of..." );
product1.setPrice( 129.99f );
product1.setImage( "assets/products/usbwatch.jpg" );
product1.setThumbnail( "assets/products/usbwatch_sm.jpg" );
products.add( product1 );

And turned it into CF code, like this:


<cfset var product1 = CreateObject('component','ProductVO')>    
<cfscript>
product1.setId( 1 );
product1.setName( "USB Watch" );
product1.setDescription( "So, you need to tell the time of..." );
product1.setPrice( 129.99 );
product1.setImage( "assets/products/usbwatch.jpg" );
product1.setThumbnail( "assets/products/usbwatch_sm.jpg" );
ArrayAppend(products, product1 );
</cfscript>

All the vars went at the top of the method, of course. Once the Value Object is created, we just call the set methods to populate it with data. I copied most of the code from Java to ColdFusion with few . I removed the 'f' at the end of price. This most likely it stood for 'float', AKA decimal numbers. The only change of the code block is in the last line. In Java, the code was being added to a link list. In ColdFusion I'm using ArrayAppend.

The Product Value Object

The product value object was simple to convert from a code standpoint. However, getting it to work with Flex was a bit tricker. Flex and ColdFusion are very finicky on how CFCs will be automatically converted to ActionScript objects. Before getting into ActionScript, first, I'll discuss the CF side of things.

There are six instance variables in the ProductVO component. An id, name, description, price, image, and thumbnail. I recreated all of them in ColdFusion. The Java code created the instance variables as private. With ColdFusion code, I had to put them in the this scope, and define the components using the cfproperty tag.

A bunch of get and set methods were in the Java version of the product value object. I wasn't sure how closely the CFC and AS files had to be related, so I converted them all from Java to CF.

The Java method had two methods, toString and equals that ColdFusion would not let me convert. You cannot create methods that have the same name as as reserved words in CF. toString and equals are the name of generic Java methods, and ColdFusion does not allow you to create methods that are similar to names of built in functions. Even though toString and equals are not built in functions, CF still throws the error. With no other alternative, I left those two methods out.

I tested my components with this code:


<cfscript>
MyObject = CreateObject('component','productdelegate');
</cfscript>

<cfdump var="#MyObject.getProducts()#">

Once I got everything the CFCs working, I moved back to the Flex code.

Defining the Services

In theory, all you need to do at this point is to tell the remoteObject tags to point at the ColdFusion code, not the Java code. Open up com/adobe/cairngorm/samples/store/business/services.mxml. This is the file that defines the services your application uses. You'll see one remoteObject for the productService and one for the creditCardService.


<mx:RemoteObject id="productService" destination="productServiceImpl" showBusyCursor="true">
</mx:RemoteObject>

<mx:RemoteObject id="creditCardService" destination="creditCardServiceImpl" showBusyCursor="true">
</mx:RemoteObject>

We could create those destinations in ColdFusion's services-config.xml file, but I instead chose to use the default ColdFusion destination. Comment out the previous lines and replace them with these:


<mx:RemoteObject id="productService"
destination="ColdFusion"
source="htdocs.Experiments.CairngormStore.ProductDelegate"
showBusyCursor="true">

</mx:RemoteObject>

<mx:RemoteObject id="creditCardService"
destination="ColdFusion"
source="htdocs.Experiments.CairngormStore.CreditCardDelegate"
showBusyCursor="true">

</mx:RemoteObject>

You'll have to modify the source attribute to with the path to your remote objects. I was hoping that after this, I'd be good to go. Unfortunately that was not the case. It would work with only this change. I was wrong. First, I forgot to add the services-config.xml to my compiler settings. In Flex Builder, bring up properties on your project, select Flex Compiler, and add this compiler argument:


-services C:\CFusionMX7\wwwroot\WEB-INF\flex\services-config.xml

Everything still wasn't working kosher. Next, open up the ActionScript value object (com/adobe/cairngorm/samples/vo/ProductVO.as). Find this line:


[RemoteClass(alias="com.adobe.cairngorm.samples.store.vo.ProductVO")]

And change it to point to your CFC:


[RemoteClass(alias="htdocs.Experiments.CairngormStore.ProductVO")]

If you built your CFC object in the same place as the Java object, you may not need to change this line.

I tested the code and it still wasn't working. Ben Forta has a great post about gotchas when doing this. make sure that the instance variables are identical in the AS file and ColdFusion file. They must also be in identical order. Check! Use full paths to the CFCs. Check! Specify RemoteClass in the AS file. Check! What's left? The answer is case sensitivity.

You want to be very careful about the case sensitivity in your paths. My CF code worked fine without case sensitivity, but Flex was being problematic. Once I addressed the case sensitive issue, I was able to get a proof of principle template working.

But within the context of the cairgorm store, Something was still wrong. I opened up the com/adobe/cairngorm/samples/store/command/GetProductsCommand.as file. I believe the framework must look for this file when invoking the GetProductsEvent.EVENT_GET_PRODUCTS event, since I could not find it explicitly called anywhere. If the fault event is getting called, you'll see the message "Products could not be retrieved" when loading the file. This means something is wrong with your remoteObject configuration.

However, if you get no errors, but nothing is happening then something else must be wrong. Or if you see other error messages, something else must be wrong. During the course of my development I experienced both situations. First I added a quick Alert to the result function to be sure that the results are being returned properly:


Alert.show( "Products retrieved!" );

That helped me verify that the result method was indeed firing. There was one line of ActionScript in the result method that I didn't understand:


var products : ICollectionView = ICollectionView( event.result );

This line creates a product variable of ICollectionView, and calls the ICollectionView constructor with the 'event.result' as an argument. Unfortunately, ICollectionView is an interface and does not appear to have a constructor. This line was throwing errors.

After some digging I found that an ArrayCollection inherits the ICollectionView methods through the ListCollectionView class. All code written against the ICollectionView interface should also work against an ArrayCollection. I replaced the ICollectionView line above with these two:


var products : ArrayCollection = new ArrayCollection ();
products.source = event.result as Array;

And bingo, everything started working.

Getting it set up

I'm not sure how much of this code I'm allowed to distribute, but my code changes, along with the Cairngorm Store and Cairngorm framework are attached to this post.

This is what you have to do to get it working:

  1. Download the file and unzip it to a web accessible directory. Create a Flex builder project pointed at that directory.
  2. Add bin/Cairngorm.swc to your Library path.
  3. Make sure that Services compiler argument is pointed to the services-config.xml for Flex.
  4. Open up services.mxml and change the attributes to point to the CFCs. Watch your case sensitivity
  5. Open up ProductVO.as and make sure that the RemoteClass directive properly points to the location of ProductVO. Watch your case sensitivity here too.
  6. Build and run your project.

Where to Next

I did notice that I couldn't complete an order in this system because the credit card validation was failing. But, it looks like CC Validation is implemented as a crap-shoot. I either didn't try enough times or have a bug in the CreditCardDelegate CFC.

I'm wondering how the value objects that Cairngorm uses will work with ColdFusion Queries, or if it's better to run a query in CF, populate an array of Value Objects, and return that array. I'll have to do tests with this in the future.

To date, I have an understanding of some parts of Cairngorm, but I do not understand how it all comes together. It's time for me to go back to reading through Steven's articles, so the next article of this series will lean back towards them.

Related Blog Entries

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Muzak's Gravatar Hey Jeff,
Great post and nice job.
Been wanting to do this myself for a while now, but you know how it is.. never the right time ;-)

ActionScript VO's don't work with CF Queries, as you have to match the ActionScript class (vo) to a Coldfusion CFC (vo).
That is what the [RemoteClass(alias="path.to.coldfusion.cfc")] in the ActionSript class is for.
So the only option is to run a query and transform that into an Array of ValueObjects (cfc vo's).

Another annoying thing with returning a CF Query is that all the fields in the query will be uppercase when accessing the CFC as a WebService (some_service.cfc?wsdl)
On top of that, returning an Array of ValueObjects doesn't work when a CF Component is accessed as WebService from Flex.
The only thing that works properly when accessing a CF Component as WebService is returning an Array of Structs (Objects).
An Array of Structs is transformed into an ArrayCollection (containing an Array of Objects).
And in order to retain the case of the field names you have to assign each table field like this (in a cfloop)

<cfset tmpObj = StructNew() />
<cfset tmpObj["nameOfField"] = nameOfField />

The square bracket notation makes sure that the variables arrive in Flex with preserved case.

regards,
Muzak
# Posted By Muzak | 2/26/07 6:38 PM
Jeffry Houser's Gravatar Muzak,

Thanks for reading and the response. I think it's easier to pass back a CF query to Flex than to convert it to VOs first. I guess, in theory, we could ignore the whole VO thing and just use Queries within Cairngorm. I'm not sure if that's a good idea or not, though.

If you're using CF and Flex, why would you try to access the CFC as a Webservice at all? The RemoteObject tag seems like a much more elegant solution.
# Posted By Jeffry Houser | 2/26/07 7:51 PM
Muzak's Gravatar For now, I'm not using VO's with ColdFusion because of the problems when accessing CF Components as WebService.
I do hope Adobe fixes the issue in the next release though.

I knew you (or someone else) were going to ask about why using WebServices in the first place.
Well, there's a few reasons I can think of.
First of all because with ColdFusion you can, without having to write any additional code (well at least that's how it should be) ;-)
Meaning, because you can access a Component both through Remoting (Flash/Flex) and as a WebService, they should work/behave exactly the same.

Imagine you have a Flash application that uses Flash Remoting + ColdFusion CFC's and as a CMS you have a Flex Application.
Add to that that the Flex version (CMS) needs to both run online and as a desktop application.
With the above in mind you can use Remoting for one app and WebServices for the other, without having to change anything on the ColdFusion side of things.

Another reason is that with Flex, you need CF 7.02 (flex remoting).
Not everyone (providers, clients) have made the upgrade yet, so I still have to settle for ColdFusion 6.xx or 7.0 quite often (I'm a freelancer).
However, if everything would work as expected, one could use WebServices now and switch to Remoting once the client or provider has upgraded, without having to make any changes to the ColdFusion side of things.
Simply swap WebService tags for RemoteObject tags in your Flex app and you're done.
Without the use of VO's that is already possible, as long as you return Array's of Structs instead of CF Queries.

With Flash I use Flash Remoting all the time and have done so since day one (when it was released).
With Flex, I mostly stick to WebServices for now, hoping that in the next CF release things will get fixed.

Currently, I don't really miss the use of VO's, I've gotten along just fine without them ;-) but it's one of those things that makes OOP just that little bit more OOP I guess.

Thanks for reading ;-)

Muzak
# Posted By Muzak | 2/26/07 9:10 PM
Jeffry Houser's Gravatar So, if i can summarize.

You use Web Services over Flash Remoting for:

a) Compatibility with Flash
b) Compatibility with earlier version of ColdFusion

Those both seem like good reasons.
# Posted By Jeffry Houser | 2/26/07 10:08 PM
Muzak's Gravatar Almost ;-)
With Flash I *do use* Flash Remoting.
With Flex I currently *do not use* Flex Remoting.

Flash 8 only requires ColdFusion 6 (CFMX).
Or put differently, Flash Remoting is AMF0, while Flex Remoting is AMF3.

So, when mixing Flash and Flex in a project, I can use Flash Remoting in the Flash application, WebServices in the Flex application, both accessing the same ColdFusion components.

That aside, I know you can use AMF0 with Flex, but that requires a custom RemoteObject version.
http://renaun.com/blog/flex-components/remoteobjec...
I think that's what most PHP'ers have been using, allthough I also think that the current AMFPHP version supports AMF3.
- just checked, it does -
http://www.5etdemi.com/blog/
http://www.amfphp.org/

With all that said, I guess one could come to the conclusion that Flex Remoting (AMF3) is not yet where it should be, which is a shame, cos it's an awesome technology.

regards,
Muzak
# Posted By Muzak | 2/26/07 10:37 PM
Keith's Gravatar Well everything was going great until coldfusion. Don't have it, and probably wont get it. My current hosting company don't offer it. So my question is
"Is there another way?" I run apache and php with mysql.
# Posted By Keith | 7/27/08 7:56 PM
Jeffry Houser's Gravatar Keith,

Flex, and Cairngorm, can be used with a PHP / MySQL backend. To use RemoteObject I believe you need to install something like AMFPHP: http://www.amfphp.org/ . Without that, you can still use the HTTPService or WebService tags to pass data back and forth.

Does that answer your question? If memory serves me, the remaining parts of this series are backend agnostic.
# Posted By Jeffry Houser | 7/28/08 8:26 AM
Zilvester's Gravatar awesome work... i was part way through your steps when i found this and got it working...: http://renaun.com/blog/2006/11/01/144/
it includes the cfc's and changes to services.mxml - that's about it as far as i can tell.
# Posted By Zilvester | 7/28/08 9:25 PM
Maggie's Gravatar Thanks so much for this! I was hitting my head for a while trying to get this to work. Just in case anyone has the same problems, these were the issues I encountered:

1. After ensuring I created the CairngormCFSTore21 directory in my web root, when I extracted the zip file, the hierarchy was not exactly the way it should be. I had to move the assets, bin, com, and data directories into the Flex Project's src directory. I then moved the META-INF, WEB-INF and libs directory into the direct Flex project root, along with all of the CFCs, the index.htm, .txt files and test.cfm file.

2. When I ran the project I got an error message stating "Products could not be retrieved!". When I closed that Alert box, there was another message underneath with the following information: FaultEvent fault=[RPC Fault faultString="Unable to invoke CFC - Variable PRODUCTS is undefined." faultCode="Server.Processing" faultDetail=""] messageId="FD50246B-450D-D3A3-6D47-22F91BEA3190" type="fault" bubbles=false cancelable=true eventPhase=2]

3. I ensured that my Services.mxml and ProductVO.as files had the correct alias (in my case that was CairngormCFStore21.ProductVO)

4. I then ran the test.cfm file and also changed to CairngormCFStore21.ProductVO and hit in in the browser (localhost/CairngormCFStore21/test.cfm). I got an error that the variable Products was undefined.

5. I went back into the ProductDAO.cfc and looked through. On line 35 there is a cfscript block that sets variables.instance.products == ArrayNew(1). This was where the old Java code had set the products initially. I went through and double-checked. Sure enough, products wasn't explicitly set, only variable.instance.products. So I changed "<cfreturn products> on lin 402 to <cfreturn variables.instance.products>

6. I then ran the Main.mxml file and it FINALLY WORKED. (BTW I'm using Flex 3 with Cairngorm 2.2.1)

I have to say I have had a miserable time working with Cairngorm thus far and definitely appreciate you doing the legwork to get this implemented. It was frustrating dealing with these errors, but I was definitely happy that I finally got this working. Thanks again!
# Posted By Maggie | 1/28/09 8:46 PM
All Content Copyright 2005, 2006, 2007, 2008, 2009 Jeffry Houser. May not be reused without permission
BlogCFC was created by Raymond Camden. This blog is running version 5.9.2.002.