By Peter Bell

The Problem with getMetadata() and annotations (and possible solutions)

ColdFusion has a great getMetadata() method for getting metadata about an object. What's better, you can add and access arbitrary properties, so if you add a title="Whatever" to your cfcomponent tag, you can #getMetadata(Object).Title# for an instance of that component and it'll return your Title property, so this effectively allows for annotations in CF as Chris Scott pointed out a while ago.

However, annotations don't inherit which causes a problem if you're using a extension to keep generated and custom code separate. Here's the problem and some possible solutions (as always, input appreciated!) . . .

A good rule of thumb is never to hand edit any generated code, so if you want to generate a UserService.cfc but still want to be able to add custom code, one of the most useful patterns is to generate GeneratedUserService.cfc, then you can put any custom code into UserService.cfc which extends GeneratedUserService.cfc. With this pattern, you can regenerate GeneratedUserService.cfc at any time without messing up any custom code you may have written in UserService.cfc.

However, if you're using annotations, they don't inherit. Try putting cfcomponent Title="whatever" into GeneratedUserService.cfc, create a UserService.cfc that extends the generated user service, instantiate it and then try calling getMetadata(UserService).Title - it'll error as the Title annotation doesn't inherit.

So, now if you want to annotate the title or db table or whatever for UserService, you're going to have to put that annotation into the user editable UserService.cfc. You could write some kind of text munger to do a "protected blocks" style transformation of the UserService, just editing the cfcomponent and any cfproperty tags on re-generation. It isn't that technically difficult, but I'm not a huge fan of the approach. For now I'm thinking the least bad solution would be to actively re-generate both the GeneratedUserService.cfc (with the code) and the UserService.cfc, making the UserService look something like this:

<cfcomponent name="UserService" extends="com.gen.model.GeneratedUserService" title="User Service">
<cfproperty . . . />
<cfproperty . . . />
<cfproperty . . . />

<cfinclude template="/com/model/UserService.cfm">
</cfcomponent>

basically using class based mixins to mix in any custom methods. Not ideal, but not too bad.

Oh, and why don't I just dispense with the inheritance altogether if I'm using mixins to put the custom code into a different file? Because if you want to change the implementation of a method, with extension, you can write another getWhatever() or validateSomething() method. If you use class based mixins and the generated method exists, it'll throw an error that the method already exists. Of course, you could fix this with some clever object based mixin coding that replaced methods one at a time, but it'd make the framework more complex and potentially have some runtime performance implications as well . . .

Any thoughts? How would/do you handle this?

Comments
Hey Peter,

Thanks for posting this! It got my attention because we're planning on using annotations to help articulate test behaviors and possibly to assist with test generation.

We're using annotations at the method level, and the thing I saw is that the metadata for a cfc comes down the inheritance tree but _none_ of a parent's metadata is available at the child's level, even methods. Walking the tree is the only way I found to get what I need:

assertEquals(getMetaData(this).extends.functions[1].mxunit, "@MyAnnotation"); //green

Walking up (or visually down) the tree, recursivley or not, and you find more than one annotation for the same element, how do you handle that? I guess it depends ...

thanks,
bill
# Posted By bill | 7/22/08 1:47 PM
Peter, forgot to ask/mention, but with the big upgrade plans for cfscript, the use of annotations in this sense may not work ...

thanks again!
bill
# Posted By bill | 7/22/08 3:15 PM
@Bill, Yeah, I think wrapping the getMetadata in a method within the object that walks the getMetadata(THIS) is the best way to handle this. If more than one annotation, the child overrides the parent, so if BaseObject has:
cfproperty name="ID" type="int" field="textbox"
and UserObject has
cfproperty name="ID" type="string" validation=Maxlength(16)

then the ID would be of type string, use a textbox as the field and be validated for a maxlength of 16 (the annotation samples are arbitrary - I just want to make the point about how the inheritance would work).
# Posted By Peter Bell | 7/22/08 3:21 PM
@Bill,

Hmm, I'm hoping as part of the cfscript spec they'll explicitly support annotations - perhaps looking to Java for an implementation syntax (not something I usually recommend :-) ).
# Posted By Peter Bell | 7/22/08 3:22 PM
Hey Guys, I am a little late to the game here but basically just to reiterate exactly what Bill and Peter have said above...I ran into this just the other day and was wondering the exact same thing. It was just for a personal project, but as I remember I just had to write a custom function, getCFProperties(metaData=getMetadata(this)), that returns a struct keyed by the property name. It does the recursion backwards, so that parent properties are overwrote by the child properties. The nice thing about CF is that while you can't have 2 cfproperties with the same name in a single CFC, you can have a property with the same name in both parent and child CFCs.

One thing i want to point out, that may be a bug (not sure), is that if you have Customer.cfc that extends User.cfc, and inside User.cfc you have <cfproperty name="phoneNumber" type="string" />, but then change that to <cfproperty name="phoneNumber" type="numeric" />, Customer.cfc still sees the property phoneNumber as having type=string even though User.cfc sees type=numeric. Customer.cfc will continue doing this until you edit Customer.cfc, causing ColdFusion to recompile it. This makes sense if we think that ColdFusion, when extending a CFC, essentially merges the two CFC's together and converts it to Java byte code. I could be extremely wrong though, I really have no clue how the ColdFusion to Java magic works. What do you think, is this apparent caching of cfproperty, and I imagine other metadata a bug or expected behaviour?
# Posted By Greg | 7/22/08 3:56 PM
@Greg - Interesting gotcha - I'll have to play with that and see what's up with it.

Don't suppose you're willing to share your code and save the rest of us straining our poor tired brains to write the recursive walker?!
# Posted By Peter Bell | 7/22/08 4:17 PM
Peter,

Here's recursive method for mxunit's assertIsTypeOf() that checks to see if the component matches any parent, all the way up to WEB-INF.cftags.component.

Look for buildInheritanceTree() - recursively builds a simple list of a component's ancestry:

http://mxunit.googlecode.com/svn/mxunit/trunk/fram...

bill
# Posted By bill | 7/22/08 4:46 PM
@Bill, Thanks!
# Posted By Peter Bell | 7/22/08 4:51 PM
@Peter, I just dropped you an email regarding the recursive code.

Just wanted to mention to everyone reading this that you may also want to check out Joe Rinehart's Bean Utils (http://beanutils.riaforge.org/). Haven't personally played with it yet, but wish I would have since it looks like it has some very useful functionality for things like this.
# Posted By Greg | 7/22/08 5:31 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.