The Problem with getMetadata() and annotations (and possible solutions)
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:
<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?



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
thanks again!
bill
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).
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 :-) ).
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?
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?!
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
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.