ColdBox Proxy Guide

Introduction

The ColdBox proxy enables remote applications or technologies like Flex, AIR and AJAX to communicate with ColdBox and provide an event driven model framework for those applications or as an enhanced service layer. Not only that, but you can reinitialize the entire application, get settings, and yes, announce custom or core interceptions. You can create custom interceptor chains for your model that can be executed asynchronously when a user hits a save record button for example. You can create a Service Layer with built-in environmental settings, logging, error handling, event interception and chaining, you name it, the possibilities are endless. The one key feature here, is that ColdBox morphs into a remote event-driven framework and no longer an MVC framework that produces HTML.

Not only that, but this enables you to actually create any amount of front ends using the same reusable ColdBox and model code. The code is the same, you create event handlers, you interact with a request collection, with core and custom plugins, but you don't set views or layouts because the framework is now a remote framework for your model. So what do you do, well, return data, arrays, xml, value objects. Anything, right from withing the event handlers or setup a configuration setting that tells the framework to always return the request collection. You can also just create remote proxies to your service components (if using service layers), so you can go directly to any object factory to request for service layers, interact with them and return results. The ColdBox proxy gives you flexibility in all aspects.

Getting Started

ColdBox provides you with a sample coldboxProxy file that can be found in the Application Template directory. This file can be seen below:

<cfcomponent name="coldboxproxy" output="false" extends="coldbox.system.extras.ColdboxProxy">

        <!--- You can override this method if you want to intercept before and after. --->
        <cffunction name="process" output="false" access="remote" returntype="any" hint="Process a remote call and return data/objects back.">
                <cfset var results = "">
                
                <!--- Anything before --->
                
                <!--- Call the actual proxy --->
                <cfset results = super.process(argumentCollection=arguments)>
                
                <!--- Anything after --->
                
                <cfreturn results>
        </cffunction>
        
</cfcomponent>

The Base Proxy Object

As you can see from the code above, this proxy inherits from the coldbox.system.extras.ColdBoxProxy class. This is the key to it all, this base class has all the necessary hooks for your proxy to work. Why is it inherited? Why not just use that one? Well, the answer is that every project is different and I believe in empowering the developer. Therefore, you have your own class in which you can expose any other remote methods as you need. You only need to know the methods that are available to you from the base class:

Method Access Returntype Description
process() remote any Processes a remote call and returns data/objects back.
announceInterception() remote boolean Processes a remote interception. Return true if successful.
verifyColdBox() private boolean Verifies that the coldbox controller has been loaded or not
getController() private ColdBox Controller Returns the ColdBox controller instance

You can use any of those methods in your own proxy or even expand them. With this in mind, you can create a set of remote objects that inherit from the ColdBox proxy that will be your proxy layer to any remote communication if necessary.

Expanding The Proxy

Below is a custom method that I created in my own application proxy for retrieving application settings. I DO NOT recommend this, since it will expose your settings. This is just an sample

<!--- Get a setting --->
<cffunction name="getSetting" hint="I get a setting from the FW Config structures. Use the FWSetting boolean argument to retrieve from the fwSettingsStruct." access="remote" returntype="any" output="false">
<!--- ************************************************************* --->
<cfargument name="name"             type="string"       hint="Name of the setting key to retrieve"  >
<cfargument name="FWSetting"    type="boolean"          required="false"  hint="Boolean Flag. If true, it will retrieve from the fwSettingsStruct else from the configStruct. Default is false." default="false">
<!--- ************************************************************* --->
<cfscript>
var cbController = "";
var setting = "";

cbController = getController();


//Get Setting else return ""
if( cbController.settingExists(argumentCollection=arguments) ){
        setting = cbController.getSetting(argumentCollection=arguments);
}

//Get settings
return setting;
</cfscript>
</cffunction>

This method basically retrieves a setting via the controller.

The Configuration File

  • AppMapping? : If using the proxy from Flex or any method via Remoting or Life Cycle Data Services or whatever Adobe renames it to, you have to set this setting so ColdBox can find your pathing and application. So always remember to set it. This setting is not needed if the remote calls are via ajax or any http protocol.
  • ProxyReturnCollection : This boolean setting determines if the proxy should return what the event handlers return or just return the request collection structure.

This can be a very useful setting, if you don't even want to return any data from the handlers (via the process() method). The framework will just always return the request collection structure back to the proxy caller. By default, the framework has this setting turned to false so you can have more control and flexibility.

See Config Guide

Important Note: If you will be using the proxy, then you MUST set the AppMapping setting because ColdBox will not be able to auto-calculate your correct instantiation paths if the first request is to the proxy via remoting. This is due to how ColdFusion redirects flash remoting or LDS requests. So please, create an AppMapping setting. If you do not know how to do this, read the Config Guide.

The Event Handlers

The event handlers that you will produce for remote interaction are exactly the same as your other handlers, with the exception of a return type (unless using the above mentioned setting). Below I layout a simple example:

<cffunction name="getCacheItemTypes" access="public" output="false" returntype="any">
<cfargument name="Event" type="coldbox.system.beans.requestContext">
        <cfset var itemTypes = structnew()>
        
        <cfset itemTypes = getColdBoxOCM().getItemTypes()>
        
        <cfreturn itemTypes>
</cffunction>

The above handler method will query the ColdBox cache for all the item types it contains and return them to the proxy. As you can see, the code is fairly simple and you can use any part of the framework lifecycle at your disposal.

Distinguishing Request Types

Now, what if you want to distinguish between a normal request and a proxy request? Well, the request context object, most commonly known as the event object has a new method called:

  • isProxyRequest : This boolean method determines what type of request is being executed.

This is extremely useful if you are doing path operations. Why? Well, when you call a coldfusion component via flash remoting or live data cycle services, the expandPath, getCurrentTemplatePath, getBaseTemplatePath, and other template path methods will always give you the path according to the file you are currently executing. This is due to how ColdFusion deals with remote calls. So you can use the above method to distinguish and load accordingly:

<!--- Use the isProxyRequest() --->
<cfif isColdfusionMX7>
        <cfif event.isProxyRequest()>
                <cfset getPlugin("JavaLoader").setup( listToArray( ExpandPath("../includes/helloworld.jar")) )>
        <cfelse>
                <cfset getPlugin("JavaLoader").setup( listToArray( ExpandPath("includes/helloworld.jar")) )>
        </cfif>
</cfif>

The above code will be executed withing the proxy as if its in the handlers directory. So to expand the jar file path in the includes directory, you have to go back a level when using the proxy and just directly if not. This is very important to grasp, since its not ColdBox doing this, but ColdFusion.

Some Flex Code

This is the last step of the guide, so let's review for a moment.

  • We have configured the application to return data/objects from the event handlers
  • We have customized our proxy and made sure it inherits from the base proxy.
  • We have created some event handler methods
  • We learned how to determine when a remote call was made via the event object

Now our last step is to actually show some remote calls. For this example, I will be showing some Flex code. I am no big Flex expert, but below are some of the flex sample codes on how to call the ColdBox proxy. I recommend encapsulating the calls to the ColdBox proxy into a class of its own as a delegate class or however you see it fit in your Flex application architecture. The sample below is very flat.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
        
<mx:Script>
<![CDATA[
        import mx.rpc.events.FaultEvent;
        import mx.events.ItemClickEvent;
        import mx.rpc.events.ResultEvent;
        import mx.controls.Alert;
        import mx.collections.ArrayCollection;
        
        //My Proxy Path
        public var cbProxyPath:String = "coldbox.samples.applications.ColdboxFlexTester.webroot.coldboxproxy";
        
        /* PUBLIC FAULT Handler */
        public function faultHandler(event:FaultEvent):void{
                Alert.show(event.fault.toString());
        }
        /* UTILITY METHOD TO GET A CBPROXY OBJECT, You can separate all this to a delagate class */
        public function getColdBoxProxy():RemoteObject{
                var cProxy:RemoteObject = new RemoteObject( "ColdFusion" );
                cProxy.source= cbProxyPath;
                cProxy.showBusyCursor = true;
                cProxy.addEventListener( FaultEvent.FAULT, faultHandler );
                return cProxy;
        }
        /* Read the Cache Objects */
        public function handleCacheResults(event:ResultEvent):void{
                var cacheItems:Object = new Object();
                var key:String;
                var array:Array = new Array();
                
                cacheItems = event.result;
                for( key in cacheItems ){
                        array.push( { item:key, total: cacheItems[key] });
                }
                var collection:ArrayCollection = new ArrayCollection(array);
                cachechart.dataProvider = collection;
        }
        /* Call the proxy for cache objects */
        public function readCache():void{
                var cProxy:RemoteObject = getColdBoxProxy();
                cProxy.process.addEventListener("result",handleCacheResults );
                cProxy.process({event:"ehFlex.getCacheItemTypes"});
        }
        ]]>
</mx:Script>

        <mx:PieChart id="cachechart"
            height="190"
            width="205"
            showDataTips="true"  x="10" y="450">
        <mx:series>
            <mx:PieSeries field="total" nameField="item"
                labelPosition="callout" />
        </mx:series>
    </mx:PieChart>
    <mx:Button x="45" y="420" label="Get Cache Chart" click="readCache()"/>

</mx:Application>

So what does this application do. Well let's start from the top. The first part of the application just imports some classes for us to use. Then we declare the path to our coldbox proxy:

//My Proxy Path
public var cbProxyPath:String = "coldbox.samples.applications.ColdboxFlexTester.webroot.coldboxproxy";

We then setup a public default error handler:

/* PUBLIC FAULT Handler */
public function faultHandler(event:FaultEvent):void{
        Alert.show(event.fault.toString());
}

We then declare our mini proxy delegate. Again, this is for sample purposes, you must encapsulate this into a class of its own and even expand on it to make it a true delegate.

/* UTILITY METHOD TO GET A CBPROXY OBJECT, You can separate all this to a delagate class */
public function getColdBoxProxy():RemoteObject{
        var cProxy:RemoteObject = new RemoteObject( "ColdFusion" );
        cProxy.source= cbProxyPath;
        cProxy.showBusyCursor = true;
        cProxy.addEventListener( FaultEvent.FAULT, faultHandler );
        return cProxy;
}

Now, you might ask, why create a delaget and not a remote object and just call it. Well, the problem lies in that you will always be calling the same method: process() on the proxy, but need to bind the results to different result handlers. Therefore, you need to create a delegate to process your request and assign a results handler for you. There are tons of ways to achieve what I am doing, I am doing the poor man's delegate.

Once I have done this, then I can create some object for me to display the cache in:

<mx:PieChart id="cachechart"
        height="190"
        width="205"
        showDataTips="true"  x="10" y="450">
    <mx:series>
        <mx:PieSeries field="total" nameField="item"
             labelPosition="callout" />
    </mx:series>
</mx:PieChart>
<mx:Button x="45" y="420" label="Get Cache Chart" click="readCache()"/>

This declares a piechart object and a push button. Once the button get's clicked on it will execute the readCache method we will cover below.

/* Read the Cache Objects */
public function handleCacheResults(event:ResultEvent):void{
        var cacheItems:Object = new Object();
        var key:String;
        var array:Array = new Array();
        
        cacheItems = event.result;
        for( key in cacheItems ){
                array.push( { item:key, total: cacheItems[key] });
        }
        var collection:ArrayCollection = new ArrayCollection(array);
        cachechart.dataProvider = collection;
}
/* Call the proxy for cache objects */
public function readCache():void{
        var cProxy:RemoteObject = getColdBoxProxy();
        cProxy.process.addEventListener("result",handleCacheResults );
        cProxy.process({event:"ehFlex.getCacheItemTypes"});
}

The readCache method basically calls the getColdBoxProxy delegate method to get a remote object for the proxy. It then creates an event listener for it and calls the proxy. The listener is called handleCacheResults which then manipulates the results and renders the cache.

Conclusion

As you can see, calling the ColdBox proxy from flex or any remote interface is very easy, but extremely powerful. Some applications for the proxy are :

  • Remote Event driven Model Framework
  • One configuration file for all application GUI's
  • One common application language for all backend operations
  • Multiple GUI's running on one single ColdBox application
  • Enhanced service layers (caching, logging, AOP, interceptions, etc)
  • Create beautiful RIA applications with a solid backend
  • Create widgets and application monitors.
  • What your imagination dictates.

Indeed, the ColdBox proxy is a great feature to complement your already great ColdBox applications. You can now use a standardized language for application development in all your front-ends.


Copyright 2006 ColdBox Framework by Luis Majano