Monday 19 May 2014

Difference between Railo and ColdFusion regarding annotating CFCs

G'day:
Yeah, I'll write some stuff up about CF.Objective() shortly, but I need to plan that a bit more before I put pen to paper. This is a quick no-brainer (so playing to my strengths ;-).

In ColdFusion 9, script-only CFCs were introduced. This is one of my favourite additions to CFML... tag based components and the tag-based syntax is a very clunky approach to code that - almost always - won't be having anything to do with mark-up.

One syntactical challenge Adobe were faced with is how to reflect the more "meta" of function and argument definitions, eg stuff like the hint, output, roles etc. The direct tag -> script syntax was pretty clunky:

<cffunction name="f" output="false" roles="special" hint="Does some stuff">
</cffunction>

Becomes:
function f() output=false roles="special" hint="Does some stuff" {
}

One expects clunky verbosity in tags, but it's a shame to have to write code like that in script.

For metadata-only type attributes - description, displayname, hint, etc - a suitable solution is to use a Javadoc style annotation, eg:

/**
* @hint This appears in the API docs for the function
* @description This is a description
* @displayName This is the displayName
*/ 
function hasMetadata() output=false roles="special" {
}

When looking at the metadata for the function, we see this (ColdFusion 9):

array
1
struct
DESCRIPTIONThis is a description
DISPLAYNAMEThis is the displayName
HINTThis appears in the API docs for the function
NAMEhasMetadata
OUTPUTfalse
PARAMETERS
array [empty]
ROLESspecial

And on Railo, this:

Array
1
Struct 
Entries: 12
access
stringpublic
closure
booleanfalse
description
string
displayName
stringThis is the displayName
hint
stringThis appears in the API docs for the function
name
stringhasMetadata
output
booleanfalse
owner
stringC:\webroots\blogExamples\railo\bugs\metacomments\ScriptBased.cfc
parameters
Array
returnFormat
stringwddx
returntype
stringany
roles
stringspecial

Note that Railo seems to not support the description attribute here.

Just before I go on, there's one thing to note here. This is fine:

/**
* @hint This appears in the API docs for the function
* @description This is a description
* @displayName This is the displayName
*/

This is not fine:
/**
* @hint           This appears in the API docs for the function
* @description    This is a description
* @displayName    This is the displayName
*/ 

The difference? Well it shows when I look at how ColdFusion processes that metadata:

array
1
struct
DESCRIPTION THISis a description
DISPLAYNAME THISis the displayName
HINT THISappears in the API docs for the function
NAMEhasMetadata
OUTPUTfalse
PARAMETERS
array [empty]
ROLESspecial

Notice how that it specifically looks for a space between the attribute and its value. Just "whitespace" doesn't count. So I cannot tab-align the values (which I'm inclined to do). Obviously (/predictably) Railo is not tripped up by this. It just works.

Anyway, as I was saying...

That's all fine, but what about the non-metadata additional params, eg: roles, output, etc which actually change runtime behaviour? These don't belong in a comment - comments should not change how code runs because they're comments, so the only "appropriate" place for these is to use the "tag styled" approach, as demonstrated earlier: as attribute/value pairs on the function definition.

It's significant to point out here that Railo have taken the sensible route here - and is at odds with ColdFusion - and does not support anything in the Javadoc block other than the metadata-type attribute values such as hint and display name. Once cannot specify "functional" parameters there such as output, access, roles etc. Any setting that impacts how code will run must be in the function declaration, not in the Javadoc block.

Here's an example:

// ScriptBased.cfc
/**
* @extends Parent
*/ 
component {

    /**
    * @access private
    */ 
    function privateFunction(){

    }

    /**
    * @returntype boolean
    */ 
    function booleanFunction(x){
        return x;
    }

}

// Parent.cfc
component {
    function parentFunction(){

    }
}

// test.cfm
o = new ScriptBased();
writeDump(getMetadata(o));

safe(function(){
    writeOutput("Running parentFunction()<br>");
    o.parentFunction();
    writeOutput("OK");
});

safe(function(){
    writeOutput("Running privateFunction()<br>");
    o.privateFunction();
    writeOutput("OK");
});

safe(function(){
    writeOutput("Running booleanFunction()<br>");
    o.booleanFunction(x=["not boolean"]);
    writeOutput("OK");
});


function safe(f){
    try {
        f();
    }catch(any e){
        writeOutput("[#e.type#] #e.message# (#e.detail#)");
    }finally{
        writeOutput("<hr>");
    }
}

On ColdFusion 11 we get this:

struct
EXTENDS
struct
EXTENDS
FULLNAMEblogExamples.railo.bugs.metacomments.Parent
FUNCTIONS
array
1
struct
NAMEparentFunction
PARAMETERS
array [empty]
NAMEblogExamples.railo.bugs.metacomments.Parent
PATHC:\webroots\blogExamples\railo\bugs\metacomments\Parent.cfc
TYPEcomponent
FULLNAMEblogExamples.railo.bugs.metacomments.ScriptBased
FUNCTIONS
array
1
struct
NAMEbooleanFunction
PARAMETERS
array
1
struct
NAMEx
REQUIREDfalse
RETURNTYPEboolean
2
struct
ACCESSprivate
NAMEprivateFunction
PARAMETERS
array [empty]
NAMEblogExamples.railo.bugs.metacomments.ScriptBased
PATHC:\webroots\blogExamples\railo\bugs\metacomments\ScriptBased.cfc
TYPEcomponent
Running parentFunction()
OK



Running privateFunction()
[Application] The method privateFunction was not found in component C:\webroots\blogExamples\railo\bugs\metacomments\ScriptBased.cfc. (Ensure that the method is defined, and that it is spelled correctly.)


Running booleanFunction()
[boolean] The value returned from the booleanFunction function is not of type boolean. (If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.)



So CF has read the comments, and its behaviour changes accordingly.

Railo does this:

Array
1
Struct
access
stringpublic
closure
booleanfalse
description
string
name
stringprivateFunction
output
booleantrue
owner
stringC:\webroots\blogExamples\railo\bugs\metacomments\ScriptBased.cfc
parameters
Array
returnFormat
stringwddx
returntype
stringany
2
Struct
access
stringpublic
closure
booleanfalse
description
string
name
stringbooleanFunction
output
booleantrue
owner
stringC:\webroots\blogExamples\railo\bugs\metacomments\ScriptBased.cfc
parameters
Array
1
Struct
name
stringx
required
booleanfalse
type
stringany
returnFormat
stringwddx
returntype
stringany
Running parentFunction()
[expression] component [ScriptBased] has no function with name [parentFunction] ()


Running privateFunction()
OK


Running booleanFunction()
OK



Because Railo does not respect the functional attributes when specified in a comment, it does not consider ScriptBased.cfc to extend Parent.cfc, it does not consider privateFunction() to be private, nor booleanFunction() to be Boolean.

I mention this because it seems to have caught someone out who is migrating from ColdFusion to Railo.

I'm all for Railo - on the whole - emulating ColdFusion's CFML. But when the ColdFusion approach is just stupid, then I'm OK fro Railo not to be stupid too.

That's it. Just tuck this into the back of your mind somewhere, so it doesn't catch you out. Don't specify stuff that impacts how your code runs in comments. Even if ColdFusion allows it. It's just dumb.

--
Adam