Ask a Jedi: Making use of ColdSpring/Model-Glue and Remote Proxies

This post is more than 2 years old.

Dean asks:

I know your big on both Model-Glue and cfajaxproxy, so I thought I would ask for your help using them together. If I have a Model-Glue app that has all of my objects defined and managed using ColdSpring, how would I call one of my service objects using the cfajaxproxy tag? All of the examples I have seen show calling the CFC's directly, but since my service object requires the injection of my DAO and Gateway objects, the direct call would fail. So I am a little stumped as to what to pass to the cfajaxproxy tag.

There are two answers to this (well I'm sure there are more, but two I'll focus on) problem. The simpler way, and the way I've done it in the past, is to not use the CFCs at all. Instead, my AJAX based actions will use normal Model-Glue events. So for example, I may have an event call getBlogEntriesJSON. This event handles broadcasting a message to get the blog entries, and then another method to handle converting the value to JSON. However, I've known for a while that ColdSpring had some support for automatically exposing itself to remote calls so I thought this would be a great opportunity to give it a try.

I began by creating a vanilla Model-Glue 2 application under webroot. I did this by editing the build.xml file within modelglueapplicationtemplate. This let me simply give the app a name, a base directory, and then right click to run it as an ANT task. I then verified that the application ran correctly. (Yes, even though I had just made a copy of the skeleton application and not touched a lick of code, I always make sure that the basic stuff works first before doing anything else!)

Next I decided on a simple model/service pair that I'd use for the demo. I created a testGateway in my model that had one method:

<cffunction name="getThings" access="public" returnType="query" output="false"> <cfset var q = queryNew("id,name")> <cfset var x = "">
&lt;cfloop index="x" from="1" to="10"&gt;
	&lt;cfset queryAddRow(q)&gt;
	&lt;cfset querySetCell(q, "id", x)&gt;
	&lt;cfset querySetCell(q, "name", "Name #x#")&gt;
&lt;/cfloop&gt;

&lt;cfreturn q&gt;

</cffunction>

I then created a service component that would wrap this:

<cfcomponent output="false">

<cffunction name="init" access="public" returnType="any" output="false"> <cfargument name="gateway" type="any" required="true"> <cfset variables.gateway = arguments.gateway> </cffunction>

<cffunction name="getThings" access="public" returnType="query" output="false"> <cfreturn variables.gateway.getThings()> </cffunction>

</cfcomponent>

And lastly, I defined them both within my ColdSpring.xml file:

<bean id="testGateway" class="testcoldspringremote.model.testgateway"> </bean>

<bean id="testService" class="testcoldspringremote.model.testservice"> <constructor-arg name="gateway"><ref bean="testGateway"/></constructor-arg> </bean>

This tells ColdSpring about my two components and also tells ColdSpring to create my service by passing in my gateway.

The last step in all of this was to then edit my controller and add a settestService method. Model-Glue supports auto wiring between ColdSpring and the controllers. By simply having setX in my controller, and X as a bean in ColdSpring.xml, I know that on startup, ModelGlue will pass an instance of X to setX. Here is the method in my controller:

<cffunction name="settestService" access="public" returnType="void" output="false"> <cfargument name="testService" type="any" required="true"> <cfset variables.testService = arguments.testService> </cffunction>

By the way, Model-Glue 3 makes this even easier. You just add beans="..." to your component tag. At this point, I wanted to test my setup. I edited my default home page event to broadcast a call to get things:

<event-handler name="page.index"> <broadcasts> <message name="getThings" /> </broadcasts> <results> <result do="view.template" /> </results> <views> <include name="body" template="dspIndex.cfm" /> </views> </event-handler>

And added the proper listener in my controller XML:

<controller name="MyController" type="testcoldspringremote.controller.Controller"> <message-listener message="OnRequestStart" function="OnRequestStart" /> <message-listener message="OnQueueComplete" function="OnQueueComplete" /> <message-listener message="OnRequestEnd" function="OnRequestEnd" /> <message-listener message="getThings" function="getThings" /> </controller>

Next I added the controller method:

<cffunction name="getThings" access="public" returnType="void" output="false"> <cfargument name="event" type="any"> <cfset arguments.event.setValue("things", variables.testService.getThings())> </cffunction>

The last thing was to edit the view:

<cfset things = viewState.getValue("things")> <cfdump var="#things#">

Wow, that was a lot of typing. Hopefully you get what I did. Basically, defined my beans in ColdSpring, wrote some dummy logic for them, then had Model-Glue actually make a call to them for my event so I could see, in the browser, the query. Just to prove I'm not crazy, here is the result:

Alright. My next stop was the ColdSpring docs. From that, it seemed like exposing my beans as remote proxies came down to two basic steps:

  1. Define (in XML) where the remote proxy will run and what it will represent
  2. Load the bean (ie, make an instance of it) so that a physical file is written out.

If we focus in on the relevant section of the docs (Using AOP to create remote proxies), our first step is to define the remote bean in XML. I won't repeat the docs. Here is what I used:

<bean id="testServiceRemote" class="coldspring.aop.framework.RemoteFactoryBean"> <property name="target"> <ref bean="testService" /> </property> <property name="serviceName"> <value>testService</value> </property> <property name="relativePath"> <value>/testcoldspringremote/remote/</value> </property> <property name="remoteMethodNames"> <value>get*</value> </property> </bean>

The only thing that tripped me up was relativePath. ColdSpring will not create the folder for you. I added a remote folder to my application root.

So the second step was to simply create an instance of the bean. So how would I do that? Well Model-Glue controllers can use an init method. You won't see them in the application template, but you can add your own to write application startup code. I added the following to my controller:

<cffunction name="init" access="public" returnType="any" output="false"> <cfargument name="ModelGlue" type="any" required="true" hint="I am an instance of ModelGlue."> <cfargument name="name" type="string" required="false" default="#createUUID()#" hint="A name for this controller."> <cfset var remoteBean = "">
&lt;cfset super.init(arguments.modelglue,arguments.name)&gt;
&lt;cfset remoteBean = getModelGlue().getBean("testServiceRemote")&gt;

&lt;cfreturn this&gt;

</cffunction>

There isn't anything too fancy in here really. The getBean method is defined in the Model-Glue docs. It's a pathway to ColdSpring and lets me grab (obviously) my defined beans. I reran my application again, went to the file system, and there it was - a file named testService.cfc.

Ok, so now for the fun part. I immediately tried to run this baby in my browser. I went to:

http://localhost/testcoldspringremote/remote/testService.cfc?method=getThings

and got...

Sorry, a ColdSpring BeanFactory named was not found in application scope. Please make sure your bean factory is properly loaded. Perhapse your main application is not running?

This didn't make sense to me at first, but then it hit me. I wasn't running Model-Glue anymore! I was totally out of the MG context, and since MG had taken care of ColdSpring for me, it wasn't available to this CFC. Luckily this is easily solved. Joe describes the solution in this blog entry: Sharing MG ColdSpring Beans With Other Applications. You should read the blog entry. Basically, I just need to initialize and setup ColdSpring myself (not a big deal!) and then tell Model-Glue to use the same application variable. I stoleborrowed the code from Joe's post exactly as he had it, with just the path to the XML file being a bit different.

After all of this, I of course went back to my normal web page, reloaded, and made sure Model-Glue was still cool. It was. (Whew!) I went back to my CFC, reloaded, and got the same error. And then I noticed it...

Sorry, a ColdSpring BeanFactory named was not

Do you see a missing word there? I looked in the error below and saw:

Sorry, a ColdSpring BeanFactory named #variables.beanFactoryName# was not found

So the code was looking for a beanFactoryName. I thought to myself - I bet that's the name of the Application scope variable that contains the factory. But since I didn't generate the service CFC, how do I set it? I couldn't find the solution in the docs, but a Google search found this blog entry by Bruce Philips. In this blog entry he mentioned a key named, appropriately enough, beanFactoryName. If you add this back in your ColdSpring.xml file, you would then end up with this definition:

<bean id="testServiceRemote" class="coldspring.aop.framework.RemoteFactoryBean"> <property name="target"> <ref bean="testService" /> </property> <property name="serviceName"> <value>testService</value> </property> <property name="relativePath"> <value>/testcoldspringremote/remote/</value> </property> <property name="remoteMethodNames"> <value>get*</value> </property> <property name="beanFactoryName"> <value>sharedBeanFactory</value> </property> </bean>

I reran everything again, went over to the CFC, and voila - I could hit my CFC!

Once I got past some of the kinks this worked like a charm, and once again I think we need to give props to both the ColdSpring and Model-Glue team.

I do have one final note on this. I'm using Model-Glue to init my bean and generate the physical file. I did that because, as I learned about this feature, that was the only way I had of handling "Do this on Application startup." Now that I ended up with actual code in the Application.cfm file, I'd probably move that bean-get call over to that file instead. It's possible that a) the proxy CFC may not exist and b) the first call may be to the remote proxy, not Model-Glue.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Dean posted on 8/23/2008 at 1:02 AM

Ray, this was exactly what I was looking for! I really appreciate you taking the time to figure this out.

Comment 2 by Nathan Mische posted on 8/23/2008 at 2:48 AM

Nice post. A couple of quick comments. First, using the Model-Glue event approach you can use ColdFire to debug your AJAX calls. Second, in the ColdSpring approach you don't need to call getBean() to generate your remote proxy. Instead you can have ColdSpring generate it by using the constructNonLazyBeans argument of the loadBeansFromXML method. Brian Kotek had a good post on this here: http://www.briankotek.com/b...

Comment 3 by todd sharp posted on 8/23/2008 at 5:07 AM

Ray - one of the best posts I've seen in a long time! Very thorough and helpful. I'd like to see a bit more, perhaps a part 2 if you get to playing with this more. Specifically I'd like to see how a DAO could be used remotely.

Would you mind zipping up the sample app and adding it to this post?

Comment 4 by Daniel Kim posted on 8/23/2008 at 10:31 AM

Awesome post. I wish I could have referenced this when I tried to get ColdSpring remoting up and running. You basically described each of the errors I ran into - I had to piece together a solution from a variety of other posts and discussion lists.

The last issue I ran into was the case sensitivity of the ColdFusion application name. It worked fine when doing HTTP requests, but when doing RemoteObject AMF calls from Flex, it couldn't find the beanFactory variable in the Application scope. At some point, after copying and pasting, I saw that the difference was in the casing of the application name in the two Application.cfcs.

Comment 5 by richard posted on 8/26/2008 at 1:44 PM

Great, comprehensive and instructive post Ray. NB a little off topic, but i spotted the appetizing snippet

"By the way, Model-Glue 3 makes this even easier. You just add beans="..." to your component tag."

I've been looking to try MG3 and have been a bit frustrated by the lack of any details about the new features - not documentation as such but maybe some kind of list of the new XML vocabulary to give a head start on the samples provided. Is there such a thing around? Doesn't need to be comprehensive

Comment 6 by Raymond Camden posted on 8/26/2008 at 3:14 PM

@richard: Joe gave a demo of Model-Glue 3 at cfobjective this year. I reviewed it here:

http://www.coldfusionjedi.c...

As it is still in development, no real docs exist yet. It makes sense for Joe to wait until things are locked down a bit more before getting the docs written.

Essentially - if I do beans="a,b" in my controller cfcomponent tag, Model-Glue will autowirte A and B into my controller. So variables.a would equal whatever A is in ColdSpring.xml.

Comment 7 by richard posted on 8/26/2008 at 11:23 PM

@ray - thanks. Sorry I hope I didn't sound mealy-mouthed (gimme,gimme,gimme) and I really appreciate all the effort going in here. Just trying to make a decision about platforms for new projects which will gestate over the next few months. Thanks

Comment 8 by Henry Ho posted on 8/28/2008 at 1:13 AM

"Basically, I just need to initialize and setup ColdSpring myself (not a big deal!) and then tell Model-Glue to use the same application variable."

It wasn't a big deal until later I found out that I need to do ?init=true almost every time I change my controller or model. I wonder if there's a way to work around that...

One thing I want to added, I wish ColdSpring remote proxy bean can support the CF8 specific features in CFFUNCTION like
"returnformat, secureJSON, and verifyClient attributes"

Comment 9 by Raymond Camden posted on 8/28/2008 at 1:16 AM

@Ho: You can turn off caching in MG. Doing though slows things down though.

As for returnFormat. DId you try it? The ability to pass returnFOrmat=JSON to a CFC is a CF thing, not a ColdSpring thing. It happens behind the scenes. So if your ColdSpring proxy returned an array and you used returnFormat=json, you should get a proper result. Now verifyCLient,secureJSON can't be passed in, but I think people will make much more use of returnFormat.

Comment 10 by Henry Ho posted on 8/28/2008 at 2:03 AM

@Raymond Camden:

turn off MG cache where? I already set reload=true, but objects that are managed by the parent Coldspring bean factory are still cached. Somehow I need to add code to capture the ?init in URL scope, and then reinitialize the beanfactore in onRequestStart() myself.

Comment 11 by Raymond Camden posted on 8/28/2008 at 2:04 AM

Oh duh - sorry. Well, if you add a simple hook to the App.cfm file (isdefined("url.init") you could manage this easily enough, right? Seems like you could use the same vals as MG for the reinit via query string operation.

Comment 12 by Henry Ho posted on 8/28/2008 at 3:08 AM

@Ray:

The MG doc for reload property says:
"When TRUE, the application will re-read the ModelGlue.xml, ColdSpring.xml and Reactor.xml files on each request, reloading the framework and using any changes made to these files."

However, by just setting reload to true in MG's coldspring.xml will not re-read the xml that ModelGlue_PARENT_BEAN_FACTORY instance uses. So additional code is needed in application.cfc/cfm to reinitialize the ModelGlue_PARENT_BEAN_FACTORY.

Solution 1: reinitialize ModelGlue_PARENT_BEAN_FACTORY only when URL.init is defined at application.cfc's onRequestStarT(). However, this is not ideal since the devloper would need to call ?init to the URL everytime the model or controller cfc is updated.

Solution 2: reinitalize ModelGlue_PARENT_BEAN_FACTORY depending on the reload property in MG's coldspring.xml. But how? use cffile and parse the mg configuration xml to get the reload value, and then reinialize ModelGlue_PARENT_BEAN_FACTORY based on the reload value? No one would want this much extra IO overhead for every request.

Solution 3: reinitalize ModelGlue_PARENT_BEAN_FACTORY in onRequestStart() depending on a boolean var in application scope, and use URL scope to set that boolean var true or false.

What solution do you use when you need your ModelGlue_PARENT_BEAN_FACTORY reinitialized?

Comment 13 by Raymond Camden posted on 8/28/2008 at 4:54 AM

I do not agree that solution 1 is unideal (is that a word?) Right now most people put MG in 'dont reload mode' early on since MG can slow down after a bit of development. So I think most devs are used to doing ?init=true during development. In fact, I'll use a tab in Firefox _just_ for that. So I'll have one tab where I'm working, and the second tab I can just hit reload on.

So that being said - I'd simply use the same QS for CS that I'd use for MG, and you can handle both with one simple reload.

Comment 14 by Chris H posted on 9/25/2008 at 8:05 PM

Hey Ray, quick question:
the generated CFC returns the data as WDDX, which sucks for a regular Ajax call receiving the data. what's the best to handle this (either by CF or JavaScript)?

Comment 15 by Raymond Camden posted on 9/25/2008 at 8:11 PM

Chris - by default, CFCs format their result in WDDX. Unless...

1) If you use returnType=xml, CF will NOT wddx-encode the result.

2) If you use returnFormat (you can pass this in the URL), you can specify plain (dont do squat), wddx (old style default), json (serialize to json).

So normally your ajax app would do: somecfc?method=getfoo&returnformat=json

Comment 16 by Chris H posted on 9/25/2008 at 8:18 PM

ah yes, forgot you could specify the returnformat in the parameters! thanks a lot!