Monday, November 28, 2005

Excellent ColdSpring posts

Sometimes the people who write the frameworks may not be the best at explaining them. Too intimate with inner workings, or perhaps too proud and wanting to divulge every - not actually fascinating - bit of alchemy involved. Lucky for me, there's some excellent blogs out there writing about ColdSpring. Scott Barnes posted Coldfusion on Cruise Control today, and yesterday Matt Woodward posted ColdSpring: Better Model Management, both great articles and should be helpful to developers just getting their feet wet with ColdSpring. Thanks guys!

Thursday, November 17, 2005

ColdSpring 0.5.0 Alpha Released!

We've just posted ColdSpring 0.5.0 Alpha over at www.coldspringframework.org containing numerous bug fixes and enhancements. The AOP framework should be significantly faster, and also contains a revised AfterThrowing Advice and MethodInterceptor. Sean Corfield's Autowire controller for ModelGlue is included along with a revised Mach-ii plugin which had autowire capabilities, as well as new Remoting helper classes and Hierarchical BeanFactory and Application Context support contributed by Kurt Wiersma

Tuesday, November 15, 2005

ColdSpring AOP Tutorial – Part Two, Around Advice

First off, sorry for the long delay in the AOP tutorials. I’ve been working pretty hard on the aop framework for a new release, and I don’t want to blog about features that are not yet available. Since we are going to do a 0.5 release this week, though, it’s time to get back on track. One thing I need to point out is that you will need to get the latest BER from CVS to try these examples, unless you already have the 0.5 release. Ok, so lets get back to where we left off. We previously talked about writing Before Advice to provide logging services to CatalogDAO. Although I didn’t discuss After Advice, we could have accomplished the same thing, with the added ability to log information about the return values from the method calls in our loggingAdvice by extending coldspring.aop. AfterReturningAdvice and putting the logging code in the method ‘afterReturning()’ instead of ‘before()’. Lets take a quick look at what that advice object would look like:

<cfcomponent name="loggingAfterAdvice" extends="coldspring.aop.AfterReturningAdvice">

  <cffunction name="setLoggingService" returntype="void" access="public" output="false" hint="Dependency: security service">
    <cfargument name="loggingService" type="net.klondike.service.LoggingService" required="true" />
    <cfset variables.m_loggingService = arguments.loggingService />
  </cffunction>
  <cffunction name="getLoggingService" returntype="net.klondike.service.LoggingService" access="public" output="false" hint="Dependency: security service">
    <cfreturn variables.m_loggingService />
  </cffunction>

  <cffunction name="afterReturning" access="public" returntype="void">
    <cfargument name="returnVal" type="any" required="false" />
    <cfargument name="method" type="coldspring.aop.Method" required="false" />
    <cfargument name="args" type="struct" required="false" />
    <cfargument name="target" type="any" required="false" />

    <cfset var rtnString = ‘’ >
    <cfset var arg = '' />
    <cfset var argString = '' />
    <cfset var objName = getMetadata(arguments.target).name />

    <cfif StructKeyExists(arguments,’returnVal’)>
      <cfif isStruct(arguments.returnVal)>
      <cfset rtnString = “ returned a struct with” & StructCount(arguments.returnVal) & “ values.” />
    <cfelseif isArray(arguments.returnVal)>
      <cfset rtnString = “ returned an array with ” & ArrayLen(arguments.returnVal) & “ values.” />
    <cfelseif isObject(arguments.returnVal)>
      <cfset rtnString = “ returned a record cfc ” />
    <cfelse>
    <cfset rtnString = “ returned ” & arguments.returnVal />
    </cfif>
  </cfif>

    <cfset variables.m_loggingService.info("[" & objName & "] " & method.getMethodName() & "() " & rtnString) />

  </cffunction>

</cfcomponent>


A few things to note here, we have to check to see if ‘returnVal’ actually exists in arguments before using it, because if the method you are advising does not return a value, ‘returnVal’ will not exist. Also, and this differs from the early alpha release, it is not necessary to return ‘returnVal’ if it exists. This value is available for you to inspect only. If you need to alter the result of method invocation, you should be working with Around Advice. In order to configure this Advice, we would take the same steps as we did with the Before Advice, configure the NamedMethodPointcutAdvisor with this Advice, and configure the ProxyFactoryBean with the CatalogDao as the target.

So now that I have covered the more simple types of Advice, let’s look at the more powerful Around Advice, called MethodInterceptor. First off, let me explain some of the motivation behind the naming convention. There is an organization called the Aopalliance that has provided standard interfaces to method interception for the development of aop frameworks. The idea being that as long as developers adhere to these interfaces, your code will be portable across different aop implementations. That sounds like a good idea to me, since who knows if someone is going to come along and write a much better aop framework that I have done. So that being said, MethodInterceptor is a type of Around Advice, meaning that it allows you to place code before and after the method call, and gives you complete control of actually calling the method. So let’s see how this works in practice, and while we’re at it I’ll cover some of the api available in the framework. This time I’m going to create an Advice to cache cfcs returned from the dao’s fetch() method, looking first to see if they exist there, and also update the cache in the save() method. This implementation is pretty simplistic and should not be viewed as a real world example, but hopefully it will get your mind going. First lets review the CatalogDAO cfc so know what methods we are going to be advising.

<cfcomponent name="CatalogDAO" output="false">

  <cffunction name="init" access="public" returntype="Any" output="false">
    <cfreturn this />
  </cffunction>

  <cffunction name="setDataSource" returntype="void" access="public" output="false" hint="Dependency: datasource name">
    <cfargument name="dsn" type="string" required="true" />
    <cfset variables.m_dsn = arguments.dsn />
  </cffunction>

  <cffunction name="fetch" returntype=" net.klondike.component.Record" access="public" output="false">
    <cfargument name="recordID" type="numeric" required="true" />
    <cfset var record = CreateObject('component','net.klondike.component.Record').init() />
    <cfset var qrySelect = 0 />

    <cfquery name="qrySelect" maxrows="1" datasource="#variables.m_dsn#">
    SELECT QUERY …
    </cfquery>

    <cfif qrySelect.RecordCount>
      <cfset record.setRecordID(qrySelect.recordID) />
      <cfset record.setArtistID(qrySelect.artistID) />
      <cfset record.setGenreID(qrySelect.genreID) />
      <cfset record.setTitle(qrySelect.title) />
      <cfset record.setReleaseDate(qrySelect.releaseDate) />
      <cfset record.setImage(qrySelect.image) />
      <cfset record.setPrice(qrySelect.price) />
      <cfset record.setFeatured(qrySelect.featured) />
    </cfif>
    <cfreturn record />
  </cffunction>

  <cffunction name="create" returntype="void" access="public" output="false">
    <cfargument name="record" type="net.klondike.component.Record" required="true" />
    <cfset var qryInsert = 0 />

    <cfquery name="qryInsert" datasource="#variables.m_dsn#">
    INSERT QUERY …
    </cfquery>
  </cffunction>

  <cffunction name="update" returntype="void" access="public" output="false">
    <cfargument name="record" type="net.klondike.component.Record" required="true" />
    <cfset var qryUpdate = 0 />

    <cfquery name="qryUpdate" datasource="#variables.m_dsn#">
    UPDATE QUERY …
    </cfquery>
  </cffunction>

</cfcomponent>


Obviously a little SQL has been removed, and I’ve also included a change in the fetch() method. We are now going to create a Record cfc from the data in the query and return that instead of a query object, so we can cache it. OK, so lets look at the around advice.

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

  <cfset variables.objectCache = StructNew() />
  <cfset variables.cacheTime = 45 />  

  <cffunction name="invokeMethod" access="public" returntype="any">
    <cfargument name="mi" type="coldspring.aop.MethodInvocation" required="true" />

    <cfset var args = arguments.mi.getArguments() />
    <cfset var methodName = arguments.mi.getMethod().getMethodName() />
    <cfset var record = 0 />
    <cfset var rtn = 0 />

    <cfif methodName IS 'fetch'>

      <cfif StructKeyExists(variables.objectCache, args['recordID']) and (DateDiff("m", variables.objectCache[args['recordID']].cached, Now()) LT 45) >
        <cflock name="simpleCachingAdvice" timeout="5">
          <cfset record = variables.objectCache[args['recordID']].obj />
        </cflock>
      <cfelse>
        <cfset record = arguments.mi.proceed() />
        <cflock name="simpleCachingAdvice" timeout="5">
          <cfif StructKeyExists(variables.objectCache, args['recordID']>
            <cfset variables.objectCache[args['recordID']] = StructNew() />
          <cfif>
          <cfset variables.objectCache[args['recordID']].cached = Now() />
          <cfset variables.objectCache[args['recordID']].obj = record />
        </cflock>
      </cfif>

      <cfreturn record />

    <cfelseif methodName IS 'save'>
      <cflock name="simpleCachingAdvice" timeout="5">
        <cfif StructKeyExists(variables.objectCache, args['record'].getRecordID() />
          <cfset variables.objectCache[args['record'].getRecordID()] = StructNew() />
        </cfif>
        <cfset variables.objectCache[args['record'].getRecordID()].cached = Now() />
        <cfset variables.objectCache[args['record'].getRecordID()].obj = args['record'] />
      </cflock>
      <cfreturn arguments.mi.proceed() />
    <cfelse>
      <cfreturn arguments.mi.proceed() />
    </cfif>
  </cffunction>

  <cffunction name="flushCache" access="public" returntype="void" output="false">
    <cflock name="simpleCachingAdvice" timeout="5">
      <cfset StructClear(variables.objectCache) />
    </cflock>
  </cffunction>

</cfcomponent>


So let’s examine what’s going on here. First we extend coldspring.aop.MethodInterceptor and overwrite invokeMethod() which receives an argument of type coldspring.aop.MethodInvocation. MethodInvocation will take care of moving through the chain of any Advice defined and then call the proxied method, by calling its proceed() method. The other public methods for MethodInvocation are getArguments() which returns the arguments you sent to the proxied method, getMethod() which returns a Method object which represents the actual method call, and getTarget() which returns the proxied object itself. From the Method cfc returned from getMethod(), getMethodName() is available as well, so you can see you pretty much have full access to the method call. In invokeMethod() above, the first thing I do is set some local variables to getArguments() and getMethod().getMethodName(), retrieving the argument collection and the name of the method. If the method name is ‘save’, I look in the local cache to see if a Record cfc exists that hasn’t expired, and if it does, I return that object instead of proceeding with the method call. If the Record instance doesn’t exist in the cache I store the result of calling proceed() in the cache. The ‘fetch’ method similarly will store the result of proceed(), and all other methods simply call proceed() without performing any additional work. There’s a very important point to take care to understand here. When using around advice, you are responsible for calling proceed() to get the method to execute and you are also responsible for returning the result of that call if necessary. This completely differs from using Before or After advice, where neither the method call or the return are your responsibility. This obviously makes Around Advice far more powerful, but you may want to think twice before using it if you don’t need to alter the method call itself. One other thing to take note of, if this particular Advice is not the last Around Advice configured, than any other Around Advice will NOT execute when I retrieve the Record cfc from the cache, because of the fact that I am not calling proceed(). This is another pitfall to be very weary of when designing apps using this technology!

So lets move on to the configuration. This time I am going simplify things a bit by skipping the setup of the Advisor all together. If you don’t care about matching the methods in a target, because your intension is to match all methods as you would with the ‘*’ pattern, you can just reference the Advice directly in the interceptorNames list for the ProxyFactoryBean. This will internally create a DefaultPointcutAdvisor for you, which will match all methods in the target. The resulting configuration file will look like the following.

<!-- set up the security advisor -->
<bean id="simpleCachingAdvice"
  class="net.klondike.aspects.simpleCachingAdvice" />

<!-- set up a proxy for the dao -->
<bean id="catalogDAOTarget"
  class="net.klondike.component.catalogDAO">
  <property name="dsn">
    <value>klondike</value>
  </property>
</bean>
  
<bean id="catalogDAO"
  class="coldspring.aop.framework.ProxyFactoryBean">
  <property name="target">
    <ref bean="catalogDAOTarget" />
  </property>
  <property name="interceptorNames">
    <list>
      <value>simpleCachingAdvice</value>
     </list>
  </property>
</bean>


You can see that this is a little more convenient, but there will be a bit of overhead in the fact that the all method calls will be processed through the interceptor instead of just the methods that actually use the cache. You may also have noticed that I also added a method to my advice that is not directly used by the aop framework, flushCache(). Just because an advice object is used by the aop framework doesn’t mean it’s not a full fledged business object! You can still retrieve the simpleCachingAdvice from the coldspring bean factory with a call to bf.getBean(“simpleCachingAdvice”) and call flushCache() in some other object in your application. This gives you an enormous amount of flexibility and power. Think about counting method calls. Have you ever put a tracking system in your apps to keep track how many users are in the system and the last thing they did? How many places in your app would that code exist, more importantly how easy would it be to turn off that system? Definitely a candidate for aop! Well I hope that I’ve sparked some more interest and brought some more of the details to light. Look for the next installment where I will cover building a suite of throws advice with the new After Throwing object, and I’ll try not to let so much time go by this time!