The amazing adventures of Doug Hughes

I am a big fan of ColdSpring. I have been for quite a while. I am also aware of the fact that I typically use only a small portion of what ColdSpring offers us. I use it mostly for bean definitions and managing dependencies. I always wanted to play around with the AOP (aspect oriented programming) pieces of ColdSpring, but I never had a real need to do so…or so I thought.

On a recent project, we had the need to have our model return data in a slightly different format when being called from a remoting proxy (also created by ColdSpring). In this case we needed to return data for use in a . Initially, we simple added a method that was solely used for AJAX calls. This can cause issues as now we had very similar code, doing a very similar process in more than once place. This can lead to issues if there were changes made to one method and not the other. The solution we came up with was to use method interception in ColdSpring.Basically, method interception, is a way that we can use ColdSpring to process method calls differently by ‘intercepting’ them.

It may seem difficult to comprehend where this would be needed, but I think the above example is a great one. Lets say you have a service object named WidgetService and inside that object is a method named getWidgets(). In our original code, we might also have a method named getWidgetsFromAJAX() that would format the data for use in the . Both of these methods would need to accept the same arguments, query the database the same way and return data. The only difference is that in the second method, we would need to call QueryConvertForGrid() before returning the results. This just feels dirty to me.

Using method interception, we can tell ColdSpring that if the method call comes from a certain file (like our remoting proxy), to handle it differently, and in this case, format the data to be used with . Lets start by going over the ColdSpring configuration.

The following is a simple bean definition for our widget service.

<bean id="WidgetService" class="com.alagad.demo.WidgetService" />

The following is our intercept definition. The interceptor names are other beans, and in this case the bean name is the bean that will be used during our intercept. This bean is also used as our ‘target’ for the remoting proxy. This is how we limit the intercept to only calls made through the remoting proxy.


<bean id="ajaxWidgetService" class="coldspring.aop.framework.ProxyFactoryBean">
 <property name="target">
  <ref bean="WidgetService" />
 </property>
 <property name="interceptorNames">
  <list>
   <value>ajaxMethodInterceptorAdvice</value>
  </list>
 </property>
</bean>

The following will create our remoting proxy. This definition tells ColdSpring to create a file named ‘RemoteWidgetService.cfc’ that is based off of the ‘ajaxWidgetService’ bean and will include the method named ‘getWidgets’ in the remoting proxy.

<bean id="remoteWidgetService" class="coldspring.aop.framework.RemoteFactoryBean" parent="abstractRemoteProxy" lazy-init="true">
 <property name="target">
  <ref bean="ajaxWidgetService"/>
 </property>
 <property name="serviceName">
  <value>RemoteWidgetService</value>
 </property>
 <property name="remoteMethodNames">
  <value>getWidgets</value>
 </property>
</bean>

The following is the simple bean definition for our intercept component. ColdSpring will use the code in this component during the intercept.

<bean id="ajaxMethodInterceptorAdvice" class="com.alagad.demo.ajaxMethodInterceptorAdvice" />

Now lets create a simple WidgetService.cfc that will return a query object from the getWidgets() method.

<cfcomponent ><br />
 <cffunction name="getWidgets" returntype="query" output="false" access="public">
  <cfset var widgets = queryNew("id,name") />
  <cfset var i = 0 />
  <cfloop from="1" to="50" index="i">
   <cfset queryAddRow(widgets) />
   <cfset querySetCell(widgets, "id", i) />
   <cfset querySetCell(widgets, "name", "widget"&i) />
  </cfloop>
  <cfreturn widgets />
 </cffunction>
</cfcomponent>

When ColdSpring creates our remoting proxy based off of this object, it will look something like this:

<cfcomponent name="RemoteAdministrationService"
   displayname="RemoteAdministrationService:RemoteProxyBean"
   hint="Abstract Base Class for Aop Based Remote Proxy Beans"
   output="false">

 <cfset variables.proxyId = CreateUUId() />
 <cfset variables.beanFactoryName = "cs" />
 <cfset variables.beanFactoryScope = "application" />
 <cfset variables.constructed = false />
 <cfset setup() />

 <cffunction name="setup" access="public" returntype="void">
  <cfset var bfUtils = 0 />
  <cfset var bf = 0 />
  <cfset var error = false />

  <!--- I want to make sure that the proxy id really exists --->
  <cfif not StructKeyExists(variables, "proxyId")>
   <cfset variables.proxyId = CreateUUId() />
  </cfif>

  <cflock name="RemoteProxyBean.#variables.proxyId#.Setup" type="readonly" timeout="5">
   <cfif not StructKeyExists(variables, "constructed") or not variables.constructed>

    <!--- it looks like there is an issue with setting up the variables scope in a static initializer
      with remote methods, so we will make sure things are set up --->
    <cfif not StructKeyExists(variables, "constructed")>
     <cfset variables.beanFactoryName = "cs" />
     <cfset variables.beanFactoryScope = "application" />
     <cfset variables.constructed = false />
    </cfif>
    <!--- make sure scope is setup (could have been set to ', meaning application, default) --->
    <cfif not len(variables.beanFactoryScope)>
     <cfset variables.beanFactoryScope = 'application' />
    </cfif>
    <cftry>
     <cfset bfUtils = createObject("component","coldspring.beans.util.BeanFactoryUtils").init()/>
     <cfif not len(variables.beanFactoryName)>
      <cfset bf = bfUtils.getDefaultFactory(variables.beanFactoryScope) />
     <cfelse>
      <cfset bf = bfUtils.getNamedFactory(variables.beanFactoryScope, variables.beanFactoryName) />
     </cfif>
     <cfset remoteFactory = bf.getBean("&RemoteAdministrationService") />
     <cfset variables.target = bf.getBean("RemoteAdministrationService") />
     <cfset variables.adviceChains = remoteFactory.getProxyAdviceChains() />
     <cfset variables.constructed = true />
     <cfcatch>
      <cfset error = true />
     </cfcatch>
    </cftry>
   </cfif>
  </cflock>

  <cfif error>
   <cfthrow type="coldspring.remoting.ApplicationContextError"
     message="Sorry, a ColdSpring BeanFactory named #variables.beanFactoryName# was not found in #variables.beanFactoryScope# scope. Please make sure your bean factory is properly loaded. Perhapse your main application is not running?" />
  </cfif>

 </cffunction>

 <cffunction name="callMethod" access="private" returntype="any">
  <cfargument name="methodName" type="string" required="true" />
  <cfargument name="args" type="struct" required="true" />
  <cfset var adviceChain = 0 />
  <cfset var methodInvocation = 0 />
  <cfset var rtn = 0 />
  <cfset var method = 0 />

  <!--- make sure setup is called --->
  <cfif not StructKeyExists(variables, "constructed") or not variables.constructed>
   <cfset setup() />
  </cfif>

  <!--- if an advice chain was created for this method, retrieve a methodInvocation chain from it and proceed --->
  <cfif StructKeyExists(variables.adviceChains, arguments.methodName)>
   <cfset method = CreateObject('component','coldspring.aop.Method').init(variables.target, arguments.methodName, arguments.args) />
   <cfset adviceChain = variables.adviceChains[arguments.methodName] />
   <cfset methodInvocation = adviceChain.getMethodInvocation(method, arguments.args, variables.target) />
   <cfreturn methodInvocation.proceed() />
  <cfelse>
   <!--- if there's no advice chains to execute, just call the method --->
   <cfinvoke component="#variables.target#"
      method="#arguments.methodName#"
      argumentcollection="#arguments.args#"
      returnvariable="rtn">
   </cfinvoke>
   <cfif isDefined('rtn')>
    <cfreturn rtn />
   </cfif>
  </cfif>

 </cffunction>

 <cffunction name="getWidgets" access="remote" returntype="any" output="false" >
  <cfset var rtn = callMethod('getWidgets', arguments) />
  <cfif isDefined('rtn')><cfreturn rtn /></cfif>
 </cffunction>

</cfcomponent>

Now whenever you use the remoting proxy, ColdSpring handles making the call to the WidgetService in your model without needing any other code.

Lastly, we need to create our intercept CFC:

<cfcomponent extends="coldspring.aop.MethodInterceptor">

 <cffunction name="init" access="private" returntype="void" output="false">
 </cffunction>

 <cffunction name="invokeMethod" access="public" returntype="any">
  <cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="true" />
  <cfset var result = 0 />
  <cfset var args = arguments.methodInvocation.getArguments() />
  <cfset var arg = 0 />

  <cfinvoke component="#arguments.methodInvocation.getTarget()#" method="#arguments.methodInvocation.getMethod().getMethodName()#" returnvariable="result">
   <cfloop collection="#args#" item="arg">
    <cfif StructKeyExists(args, arg)>
     <cfinvokeargument name="#arg#" value="#args[arg]#" />
    </cfif>
   </cfloop>
  </cfinvoke>

  <cfif IsDefined("result") AND IsQuery(result)>

   <cfif StructKeyExists(args, "page") AND StructKeyExists(args, "pageSize")>
    <cfset result = queryConvertForGrid(result, args.page, args.pageSize ) />
   </cfif>

  </cfif>

  <cfreturn result />
 </cffunction>

</cfcomponent>

This code is what actually gets run during the intercept. What we do here is use to make the method call and pass along any arguments using . Then, as long as the result of that method call is a ColdFusion query object, we call the queryConvertForGrid() method and return that value. In our project, we had a lot more code as we needed to massage some of the values returned before shipping it off to .

This example uses a bit of ColdSpring’s functionality, so lets break it down one more time.

First, we have a simple service, named ‘widgetService’ that has a method named ‘getWidgets’. This method returns a ColdFusion query object containing the id and name of the widget.

Next we have a bean named ‘ajaxWidgetService’ that defines our intercept and tells ColdSpring to use the ‘ajaxMethodInterceptorAdvice’ bean as an interceptor.

Then we create our remoting proxy by telling ColdSpring what bean to base the proxy on.

Lastly, we have the bean that will be used for our intercept.

Now lets follow the path of execution. We make a remote call to the ‘getWidgets’ method on the ‘remoteWidgetService’. This in turn calls the ‘getWidgets’ method on the widgetService, however, ColdSpring intercepts this method call, and instead, calls the ‘invokeMethod’ method on our interceptor. This method then makes the call to the ‘target’, which in this case is the ‘widgetService’. If the result of this method call is a ColdFusion query, we run call the queryConvertForGrid() method and finally return that result.

Comments on: "Using ColdSpring Interceptors With Remoting Proxies" (6)

  1. Maybe I’m missing something here… I certainly could be… this just seems amazingly complex to me, and at least at first glance I’m not really seeing the benefit. I’m just thinking about the original problem of converting data for use by cfgrid in a web service and how I would likely approach that problem…

    In my case I’m likely to be using the onTap framework (due to my obvious bias) and so I’ll have a webservice CFC in my app that has whatever arguments it needs to call to the widgetService.getWidgets() method.

    Then in my webservice I’d just be doing something like

    set var svc = getIoC().getBean(“widgetservice”);
    set var qry = svc.getWidgets(argumentcollection=argumens);

    if (isQuery(qry)) {
    … do massaging …
    qry = QueryConvertForGrid(qry);
    }

    return qry

    So rather than having some XML config and a new extra CFC to create, all the logic for converting the data for the ajax call would be built into that web service where the ajax call is being made. Am I missing something?

    I will say that it’s been a long while since I’ve used a cfgrid for anything and I’ve never used the ajax integration with it, so it’s not like I’m an expert on this or anything.

    Thanks

    Like

  2. Scott Stroz said:

    @Ike – Then how would you suggest handling the case where you need to get all the widgets, but it is not from a remote AJAX call? That would take either a seperate method, or passing in an argument that tells the method whether or not we want the query returned for the grid or for just all the widgets. Neither of these really feels like the best solution.

    As I mentioned, in our case, we also needed to do other processing on some of the data before it was sent off to be used in the .

    Like

  3. This seems to me like a lot more code, overhead and maintenance than having 2 different methods in the web service.

    cffunction name=”massageData” access=”private” returntype=”query” hint=”massage query data”
    cfargument name=”query” required=”true”
    … do data massaging …
    cfreturn query

    cffunction name=”getWidgets” access=”remote” returntype=”query” hint=”return widgets”
    set var svc = getIoC().getBean(“widgetService”);
    set var qry = svc.getWidgets();
    return massageData(qry)

    cffunction name=”getWidgetsForCFGRID” access=”remote” returntype=”query”
    return QueryConvertForGrid(getWidgets());

    At most it seems like you’d end up with a couple of duplicated cfargument tags. That’s why I’m still confused as to what the advantage of the proxy is. It seems like a lot more work to me than maintaining a handful of duplicated cfargument tags. Also seems like there would be more dependencies and it would execute slower.

    I’m not just trying to be difficult, it’s just not something I’ve worked with. I could be totally off-base, that’s why I’m asking.

    Thanks Scott, I do appreciate your taking the time to both write the blog and put up with my questions. 🙂

    Like

  4. i understand the use of AOP for security, logging, etc. across a whole application and i’m looking into it now, but i have to agree with ike, this seems pretty overkill for a simple QueryConvertForGrid(). especially if a function with one line will do the same.

    Like

  5. Christine said:

    @Scott – I’ve been traditionally dealing with this type of issue with a similar solution to Ike. What is the difference between the two solutions? Is there some type of concern, theory or design pattern issue that makes the ColdSpring intercept more “correct” or advantageous?

    Like

  6. Marcuski said:

    http://concealer.mybrute.com
    Check out this cool mini fighting game

    Like

Comments are closed.

Tag Cloud

%d bloggers like this: