Monday, January 29, 2007

Unlocking the power of CF Metadata

Lately I have been thinking an awful lot about metadata. We have this great metadata object in CF, but I rarely hear anything about it. Funny, even though we use metadata extensively in ColdSpring, I sort of forgot it was there for day to day use. ColdFusion's metadata object is free for the use in almost any way we want, and can provide extremely powerful class or method level annotations. Let's take a quick look at an example so you can see what I'm getting at. Write yourself any cfc, and write a simple cfm page to dump it's metadata:

<cfset obj = CreateObject('component','com.foo.mycfc').init() />
<cfdump var="#getMetadata(obj)#" />


This will output a big struct with all sorts of information about your object, like its name, extends, path, and lots of information its functions, like name, access, etc. There's a whole lot of great information for you to use, but what's really cool is, you can put your own stuff in there. Just make sure you don't try to overwrite any attributes that ColdFusion uses. This can be easily accomplished by using a prefix to create your own namespace. For my examples we'll use 'cs_' for our attributes. So lets add some custom metadata to our cfc. In the cfcomponent tag, lets add an attribute 'cs_metadata="really cool stuff"' (<cfcomponent name="mycfc" cs_metadata="really cool stuff"...) and run the cfm page we made in the previous example. Check out the output, now we have a cs_metadata key with the value "really cool stuff". Really cool, huh? Or are you thinking, so what? The thing that got me all excited about this is, if we use annotations on methods in our cfcs, we can access them in Advice components (you knew I was gonna get to some AOP right?), which is in fact really really cool stuff.

The great thing about AOP is it gives you the ability to develop abstract systems in an extremely generic manner. But when we apply them to specific components, we want them to take on more concrete functionality. The easiest way to do this is to provide your Aspects with parameters during configuration, enabling them to become more specialized to your components. For example, in the Klondike record store example I have been showing in my sessions, I have a generic caching system which can be configured for each type of component to cache by providing information to the Aspects. But the downfall of this is, I have to configure that Aspect over and over in ColdSpring for each model component. Enter metadata!

Let's take a look at a super simple system, one that always show up in any AOP conversation, logging. It's pretty easy to write a very general logging system. I like to wrap the logging in a service so we can reuse it anywhere, and provide log level methods, info(message), warn(message), error(message), debug(message), this way I can swap out cflog for log4j and control the log level in one place. Now all we need is an Aspect that will build a log statement from a method call. What if we would like more control, say to only include certain parameters for certain methods. This would be a nightmare if we tried to provide some sort of configuration to the Aspect, but we can easily put this info in the function metadata and read in in the Aspect! Here's how it's done, first we'll add some metadata to a cfc that we want to log method calls, notice I have added a cs_LogFields attribute:

<cfcomponent displayname="EntryService">

  ... init method and setters for dependencies ...

  <cffunction name="getEntriesByCategoryID" access="public" returntype="array" cs_LogFields="categoryID" output="false" >
    <cfargument name="categoryID" type="numeric" required="true" />
    <cfargument name="numToReturn" type="numeric" required="false" default="-1" />
    <cfargument name="activeOnly" type="boolean" required="false" default="true" />
  
    <cfreturn variables.entryGateway.getEntries(arguments.numToReturn, arguments.activeOnly, arguments.categoryID) />
  </cffunction>

  ... more service methods ...

</cfcomponent>


Next we have our logging service, I've used cflog in this example and added a setter for the log file name.

<cfcomponent displayname="LoggingService">

  <cffunction name="init" returntype="net.litepost.component.logging.LoggingService" access="public" output="false">
    <cfreturn this />
  </cffunction>

  <cffunction name="setFilename" access="public" returntype="void" output="false">
    <cfargument name="filename" type="string" required="true" />
    <cfset variables.filename = arguments.filename  />
  </cffunction>

  <cffunction name="info" access="public" returntype="void" output="false">
    <cfargument name="message" type="string" required="true" />
    <cflog file="#variables.filename#" type="information" text="#arguments.message#">
  </cffunction>

  ... rest of logging methods ...

</cfcomponent>


So now we will need an Advice class to tie it all together. I am going to use an AfterReturningAdvice for this one, it's going to be a bit more complex, so I'll put some comments in line.

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

  <!--- setters for the logging service --->
  <cffunction name="setLoggingService" returntype="void" access="public" output="false"
        hint="Dependency: security service">
    <cfargument name="loggingService" type="net.litepost.service.LoggingService" required="true"/>
    <cfset variables.m_loggingService = arguments.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 arg = '' />
    <cfset var argString = '' />
    <cfset var logFields = "" />
  
    <!--- first I need the metadata for the target object of the method call --->
    <cfset var objMd = getMetadata(arguments.target) />
    <cfset var objName = objMd.name />
  
    <!--- I also need the name of the method that was called --->
    <cfset var methodName = method.getMethodName() />

    <!--- I will use a private function to retrieve the metadata for that function --->
    <cfset var functionMd = getFunctionMetadata(objMd,methodName) />
  
    <!--- We use StructKeyExist to find our custom attribute, which we use as the logFields. If the attribute is missing, we are going to assume we want to log all the arguments to the method call --->
    <cfif StructKeyExists(functionMd, "cs_LogFields")>
      <cfset logFields = functionMd.cs_LogFields>
    <cfelse>
      <cfset logFields = StructKeyList(args)/>
    </cfif>
  
    <!--- now all we need to do is loop over our logFields list, and build out a string from those values (restricting to simpleValues) --->
    <cfloop list="#logFields#" index="arg">
      <cfif StructKeyExists(args, arg) and isSimpleValue(args[arg])>
        <cfif len(argString)>
          <cfset argString = argString & ', ' />
        </cfif>
        <cfset argString = argString & arg & '=' & args[arg] >
      </cfif>
    </cfloop>
  
    <!--- Now send it to the logging service! --->
    <cfset variables.m_loggingService.info("[" & objName & "] " & method.getMethodName() & "(" & argString & ") called!") />

  </cffunction>

  <!--- here's our private method to retrieve just a particular function metadata from the full metadata from a cfc --->
  <cffunction name="getFunctionMetadata" access="private" returntype="struct" output="false">
    <cfargument name="metadata" type="struct" required="true"/>
    <cfargument name="function" type="string" required="true"/>
    <cfset var ix = 0/>
    <cfset var mdFunctions = arguments.metadata.functions/>
  
    <cfloop from="1" to="#ArrayLen(mdFunctions)#" index="ix">
      <cfif mdFunctions[ix].name EQ arguments.function>
        <cfreturn mdFunctions[ix]/>
      </cfif>
    </cfloop>
    <cfreturn StructNew()/>
  
  </cffunction>

</cfcomponent>


If you've followed any of my previous AOP posts, you should have no problem setting all this up in your ColdSpring config file. I'll also include those links. This may look a bit complex at first sight, but look what we are gaining now. I could have provided setters to my advice class, allowing the application developer to configure it to specific uses, but that would mean they would need to configure this class over and over again for each different use, as I did for my caching system. By creating my advice class to read metadata, though, I have still provided that same customization, but this time with minimal work for the developers. Plus, we have provided the power of configuration right where the developer is working, in the cfcs themselves. This may not be the best way to go for all systems by any means, I still feel that the real power of ColdSpring AOP is its declarative approach, and it can be argued that this is going in the opposite direction. But the sheer power of this approach can open many doors to designing truly generic systems. For me that is the ultimate goal of AOP.

Happy programming, and I hope to see some readers at the frameworks conference where I plan to get a lot more in-depth into these concepts both in my session and drinking in the bar! See you!

Introduction to ColdSpring AOP
ColdSpring AOP Tutorial – Part One
ColdSpring AOP Tutorial – Part Two, Around Advice
Introducing auto-magic Remoting with ColdSpring

Friday, January 26, 2007

I'm the quintessential CF Developer!!

WOW! I can't believe it! I'm actually the quintessential CF Developer, as far as Tim Buntel is concerned! He was a little off on his meme though, so I thought I would update it, make it a little more accurate:

Chris, the web developer persona
  • 38 years old
  • Married for 7 years, has a 5.5 year old son named Tommy and a 3 year old named Simon. They both are insane about Star Wars Legos
  • Drives a 2004 Honda Accord (which the wife stole), 1998 Volvo V70, and 1999 BMW r1100s (which is the greatest feat of engineering ever created. And it's yellow, oooh yellow...)
  • Bought a house a 6 years year ago, a 1885 Stone Victorian, slowly renovating. Decorations are partially vintage, but not really.
  • Likes the "I'm a Mac, I'm a PC" tv commercials, hates the UPS "Whiteboard" ads. OK, Right on 100%. Except I like the PC guy and the Mac guy is a pompous twit. But I have been a hard core Mac enthusiast my whole life. Hey we had a Lisa for christ's sake!
  • Couldn't give a rat's ass about the Oscars
  • Into How It's Made, Mythbusters, secretly watches American Chopper even though I hate it, random nature and science stuff
  • Likes snowboarding. Wait, really likes snowboarding, really. I actually started when they were planks of wood with a rope on the nose and freaking water ski bindings. (And yeah, we wore an onion on our belt)
  • Food? A little bit of everything, especially if it's on the grill
  • Music? Let's put it this way. I added American Hardcore to my netflix queue last night, and am giddy as a school girl waiting for that damn movie to actually come out on DVD. Seriously I cannot even believe I missed it in the theater. And the new Slayer is AMAZING
  • I lost a lot of hours watching Devo videos on youtube. I do not know why...
Help Tim, what's your persona??

Some Excellent Flex articles...

Kind of late on this, but in case you missed em, a couple of Cynergy coworkers / Flex super gurus have written some must read flex articles you probably want to check out.

First up, Andy Trice did a killer mashup with Yahoo Maps and StrikeIron's census web service. Really cool demo, and of course Andy's blog is pretty much a must read.

Andy and Keun Lee also wrote a great article, second in a series on Building RIAs from front to back at Adobe's Dev center. Part one was also written by Carson Hager and Dave Wolf, Cynergy execs and killer developers too. Some really great stuff.

Monday, January 01, 2007

Happy New Year! (From ColdSpring too!)

Happy new year everybody! Or should I just list out my limited readership by name? 2006 was a very crazy year for me, good in a lot of ways, completely crazy in others, so good riddens, but only sort of. I thought that today would be a really good day to sit back and relax, but instead I decided to work on some ColdSpring. Oh well. I'll relax later. So does that mean there's something new to announce? You bet! And you can thank Sean for bugging me, too. ColdSpring now supports the tag! Here's how it's used:

Lets say you have a bean defined, perhaps a reactor factory like the following, which you have referenced all throughout a complex configuration file:

<bean id="myReactorFactory" class="reactor.reactorFactory">
    <constructor-arg name="configuration">
        <value>/config/reactor.xml
    </constructor-arg>
</bean>

Now let's say you have switched architectures to something like Model-Glue:Unity, and you want to use that same reactor factory. But there is a problem, because Unity wants that bean to be named 'ormService'. Previously you would need to change the bean's id, and then all the bean ref tags, but now can just simply define an alias, like so:

<alias name="myReactorFactory" alias="ormService"/>

Walla! Done and done. Now that's just damn handy! This is still a work in progress, so feedback and testing would be great. Happy New Year (again)!