using a query as an argument to a cffunction - coldfusion

I would like to pass a query as a parameter to a function.
According to the docs You can specify query as an argument type.
However, when I try doing this:
<cfquery name="share_types_query" datasource="phonebook">
SELECT share_loan_type, share_loan_description
FROM shareloantypes
WHERE share_loan='S'
ORDER BY share_loan_type
</cfquery>
<cfscript>
phonebookQuery(share_types_query, "SHARE_TYPES");
</cfscript>
<!--function to take a query and turn the result into a JSON string-->
<cffunction name="phonebookQuery" access="public" returnType="void">
<cfargument name="query" type=query required="yes">
<cfargument name="jsVar" type=string required="yes">
<cfset json = "[ "/>
<cfloop query="query">
<cfset json = json & "{ "/>
<cfset i=1/>
<cfloop list="#arrayToList(query.getColumnList())#" index="col">
<cfset json = json & '"#col#"' & ": "/>
<cfset json = json & '"' & query[col][currentRow] & '"'/>
<cfif i lt ListLen(query.columnList)>
<cfset json = json & ", "/>
</cfif>
<cfset i= i+1/>
</cfloop>
<cfset json = json & " }"/>
<cfif currentRow lt recordCount>
<cfset json = json &","/>
</cfif>
</cfloop>
<cfset json = json & " ]"/>
<script type="text/javascript">
<cfoutput>
var #toScript(json,Arguments.jsVar)#;
</cfoutput>
</script>
</cffunction>
I get the following error:
The parameter SELECT to function phonebookQuery is required but was not passed in.
I am very new to CF and I am on CF MX 7.

The version of the question that I posted wasn't actually a carbon copy of the function in the source file. The function in the source actually had some <cfargument> tags (the first one being name="select") that were commented out.
I was commenting them like this: <!-- a comment --> which triggered the appropriate response in my syntax highlighter, but ColdFusion was still trying to execute the lines.

Related

ListRemoveDuplicates result has a comma at the end

When using listRemoveDuplicates in Lucee, it removes the duplicate values but still leaves the delimiter in end of the value.
Ex:
<cfset myUsers = 'sathish,sathish'>
<cfset removeDups = listRemoveDuplicates(myUsers)>
<cfdump var="#removeDups#" />
This produces the output:
sathish,
However, when checking this same function with Adobe ColdFusion, it produces the correct value (no comma at the end):
sathish
How can I omit the comma delimiter in Lucee?
Surfing the Lucee documentation, I found the ListCompact() function which returns the correct value.
<cfset myUsers = 'sathish,sathish'>
<cfset removeDups = listCompact(listRemoveDuplicates(myUsers))>
<cfdump var="#removeDups#" />
You can also create a custom function like this:
<cffunction name="UDF_listRemoveDuplicates" access="public">
<cfargument name="list" type="string" required="yes">
<!--- Hash Map to maintain the order --->
<cfset local.hashMap = createObject("java", "java.util.LinkedHashMap").init()>
<!--- Add list values as key to Hash Map --->
<cfloop list="#arguments.list#" index="local.value" delimiters=",">
<cfset local.hashMap[local.value] = 1>
</cfloop>
<!--- Return list of Keys --->
<cfreturn structKeyList(local.hashMap, ",")>
</cffunction>
<cfoutput>#UDF_listRemoveDuplicates("sathish,sathish")#</cfoutput>
This should work on both. (Not tested on Lucee)

Getting data from the function using an Object in CF

I have a CFC object and a function which gets me the data which I want. Now I want to use that data and provide it to an already defined custom tag attribute. When I dump the #iEngine.listScore()# I get some parameters. But my problem is how should I provide those to an attribute?
<cfdump var="#iEngine.listScores()#" label="Swapnil Test - Function ListScore">
<cfset filename="ACE_DataExtract_#DateFormat(now(),'dd.mmm.yyyy')#.xls" />
<!--- Calling Custom tags to create/output xls files --->
<cfmodule template="#request.library.customtags.virtualpath#excel.cfm" file="#filename#" sheetname="ACE Report">
<cfmodule template="#request.library.customtags.virtualpath#exceldata.cfm"
query="#iEngine.listScores()#"
action="AddWorksheet"
sheetname="ACE Report"
colorscheme="blue"
useheaders="true"
contentformat="#{bold=true}#"
customheaders="#ListScore#">
<cfoutput>Excel Extract - ACE Report - #DateFormat(Now(),"d-mmm-yyyy")#</cfoutput>
</cfmodule>
</cfmodule>
Here I want to provide the data of iEngine.listScore() to the "Query" attribute in "exceldata" custom tag.
Below is the dump of iEngine.listScore()
I would write a transform Data function to change your array-struct to a query object, then pass that on....
<cffunction name="transformData" result="query">
<cfargument name="inArray" type="array">
<cfset local.qryReturn = queryNew("actiondate,actionId,closedate")>
<!--- You may look up queryNew and also set your dataTypes --->
<cfloop array="#arguments.inArray#" index="i">
<cfset QueryAddRow(local.qryReturn)>
<cfset querySetCell(local.qryReturn,"actionDate",i["actiondate"])>
<cfset querySetCell(local.qryReturn,"actionid",i["actionid"])>
<cfset querySetCell(local.qryReturn,"closedate",i["closedate"])>
</cfloop>
<cfreturn local.qryReturn>
</cffunction>
<cfset test = [
{actiondate='1/1/2015',actionid=134,closedate=''},
{actiondate='1/2/2015',actionid=135,closedate=''},
{actiondate='1/3/2015',actionid=136,closedate=''}
]>
<cfdump var="#test#">
<cfset resultQry = transformData(test)>
<cfif NOT isquery(resultQry)>
Exit invalid Data.
<cfelse>
<cfdump var="#resultQry#">
</cfif>

Coldfusion web service consumed as WSDL chokes on control characters

Clients upload files in .doc format to a server directory, and the text within them is extracted using POI as per Ray Camdens posting here The content is saved in a text/memo field in a MySQL database and is made available as a web service which is consumed as wsdl. All works as intended, until consumers of the web service access records containing certain (I presume) control characters, at which point the web service throws a 500 error.
In the database the problem rows seem to have control characters, and when the text field is displayed in Firefox there's odd characters too.
The web service simply returns a CF query of returntype = "any" and is called as
<cfinvoke webservice="https://nww.someplace.nhs.uk/cfcs/providerapi.cfc?wsdl"
method="getPendingReferrals" returnvariable="getReferrals">
<cfinvokeargument name="userName" value=#username#>
<cfinvokeargument name="password" value=#password#>
<cfinvokeargument name="maxrows" value=#maxrows#>
</cfinvoke>
I presume the WSDL cannot transmit these characters, so is there a way to encode them, or do I just have to strip them out using a regex or something?
<cfcomponent>
<cffunction output="false" access="remote" returntype="any" name="getPendingReferrals">
<cfargument required="false" name="userName" type="string"/>
<cfargument required="false" name="password" type="string"/>
<cfargument required="false" name="maxrows" type="numeric" default="20"/>
<cfset var q="">
<cfinvoke component="cfcs.security" method="checkAuthenticated" returnvariable="checkAuth">
<cfinvokeargument name="username" value="#arguments.userName#">
<cfinvokeargument name="password" value="#arguments.password#">
</cfinvoke>
<cfif checkAuth.authenticates is "true">
<!--- log the login --->
<cfset filename=#datepart("yyyy", now())#&#datepart("m", now())#&#datepart("d", now())#&"loginlog.txt">
<CFSET OUTFILE = "#application.Root#"&"logs\"&"#filename#">
<cfif #FileExists(OUTFILE)# is "Yes">
<cffile action="append" file="#OUTFILE#" output="#checkAuth.userName#, #now()#, #remote_addr#, #Left(http_user_agent, 50)#">
<cfelse>
<CFFILE action="write" output="#checkAuth.userName#, #now()#, #remote_addr#, #Left(http_user_agent, 50)#" file="#OUTFILE#">
</cfif>
<cfif checkAuth.organisationID is 1>
<cfset toStr="toID=1">
<cfelseif checkAuth.organisationID is 28>
<cfset toStr="(toID=28 OR toID=29)">
</cfif>
<cfquery name="q" datasource='mySqlData' maxrows=#arguments.maxrows#>
SELECT messages.messageID, messages.toID, messages.fromID AS referrerID, (SELECT CONCAT(title, ' ',firstName, ' ', lastname) FROM users WHERE users.userID = messages.fromID) as referrerName,messages.threadID, messages.messageBody, messages.dateCreated, messages.dateSent,
messages.deleted, messages.createdByID, (SELECT CONCAT(title, ' ',firstName, ' ', lastname) FROM users WHERE users.userID = messages.createdByID) as createdByName, (SELECT organisationName FROM organisations WHERE messages.originatingOrganisationID = organisations.organisationID) as originatingOrganisationName, messages.originatingOrganisationID, messages.viewed, messages.referral, messages.actioned, messages.patientID, messages.refTypeID, messages.specialtyID, organisations.organisationName AS toOrganisationName, patients.nhsNumber AS patientNHSnumber, patients.patientTitle, patients.patientLastname, patients.patientFirstname, patients.patientDOB, patients.address1 as patientAddress1, patients.address2 AS patientAddress2, patients.address3 AS patientAddress3, patients.address4 AS patientAddress4, patients.postcode AS patientPostcode, patients.patientPhone1
FROM users INNER JOIN (organisations INNER JOIN (patients INNER JOIN messages ON patients.patientID = messages.patientID) ON organisations.organisationID = messages.toID) ON users.userID = messages.fromID
WHERE #toStr#
AND NOT actioned
AND NOT originatingOrganisationID=3
ORDER BY messageID
</cfquery>
<cfif isQuery(q)>
<cfreturn q>
<cfelse>
<cfreturn "Error : in query">
</cfif>
<cfelse>
<cfreturn "Error : failed to authenticate">
</cfif>
</cffunction>
You should probably strip out all high-ascii characters using a regular expression. One of the best I've found was written up by Ben Nadel, here. (Though it wasn't perfect, and I made some improvements to it in the comments.)
Basically, if you just want to strip the high-ascii characters out, do this:
<cfset result = reReplace(messageBody, "[^\x20-\x7E\x0D\x09]", "", "all") />
This regular expression takes a white-list approach, allowing only printable characters to remain:
\x20-\x7E = {space} ! " # $ % & ' ( ) * + , - . / 0-9 : ; < = > ? # A-Z [ \ ] ^ _ ` a-z { | } ~
\x0D = carriage return
\x09 = horizontal tab
If you like this approach to sanitization, you could use Sean Coyne's method for updating the query with a loop:
<cfloop query="q">
<cfset querySetCell(
q,
"messageBody",
clean(q.messageBody[q.currentRow]),
q.currentRow
)/>
</cfloop>
<cffunction name="clean">
<cfargument name="in" />
<cfreturn reReplace(arguments.in, "[^\x20-\x7E\x0D\x09]", "", "all") />
</cffunction>
It is not ideal, but you could try something like:
<cfloop query="q">
<cfset querySetCell(q,"messageBody",xmlFormat(q.messageBody[q.currentRow]),q.currentRow) />
</cfloop>
If xmlFormat fails to remove all the characters (it has been known to miss a few), you may need to write a manual method to strip them out.
As Sean is mentioning you will need to escape all kind of special characters to get valid XML - have a look at http://www.petefreitag.com/item/202.cfm for example

Consuming a webservice code simplification

UPDATED CODE TO LATEST ITERATION
The following function consumes a webservice that returns address details based on zip code (CEP). I'm using this function to parse the xml and populate an empty query with the address details. I would like to know if there is a more elegant way to achieve the same result. It seems to be a waste to create an empty query and populate it...
Any ideas could my method be modified or the code factored/simplified?
<!--- ****** ACTION: getAddress (consumes web-service to retrieve address details) --->
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<!--- Defaults: strcep (cep (Brazilian zip-code) string webservice would look for), search result returned from webservice --->
<cfargument name="cep" type="string" default="00000000">
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0>
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<cftry>
<!--- Consume webservice --->
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#"></cfhttp>
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText>
<cfset tmp.streetName = nodes[1].nome.XmlText>
<cfset tmp.area = nodes[1].bairro.XmlText>
<cfset tmp.city = nodes[1].cidade.XmlText>
<cfset tmp.state = nodes[1].uf.XmlText>
<cfset tmp.cep = arguments.cep>
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(tmp.state)>
<cfset tmp.stateid = stateid.id>
<cfreturn tmp>
</cfif>
<!--- Display error if any --->
<cfcatch type="any">
<cfoutput>
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfoutput>
</cfcatch>
</cftry>
</cfif>
</cffunction>
<!--- ****** END ACTION getAddress --->
The calling code:
<!--- Get address data based on CEP --->
<cfset session.addressData = getAddress(cep=params.newMember.cep)>
I can't test this because I don't have an example XML file / CEP to test with, but here is a minor rewrite that addresses four things:
Instead of using cfparam and some strange "params" structure, you should pass the CEP into the function as an argument.
The function shouldn't directly modify session data. Instead, you should return the result and let the calling code assign it to the session (or wherever else it might be needed). I'll show this in a 2nd code example.
Cache the xml result per CEP -- assuming this doesn't change often. (You'll have to improve it further if you want time-based manual cache invalidation, but I can help add that if necessary)
Don't use StructInsert. It's not necessary and you're just writing it the long way for the sake of writing it the long way. There is no benefit.
Again, this isn't tested, but hopefully it's helpful:
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) --->
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0 />
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<cfif not structKeyExists(application.cepCache, arguments.cep)><!--- or cache is expired: you'd have to figure this part out --->
<!--- Consume webservice --->
<cftry>
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" />
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText />
<cfset tmp.streetName = nodes[1].nome.XmlText />
<cfset tmp.area = nodes[1].bairro.XmlText />
<cfset tmp.city = nodes[1].cidade.XmlText />
<cfset tmp.state = nodes[1].uf.XmlText />
<cfset tmp.cep = arguments.cep />
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(session.addressData.state)>
<cfset tmp.stateid = stateid.id />
</cfif>
<cfreturn duplicate(tmp) />
<!--- Display error if any --->
<cfcatch type="any">
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfcatch>
</cftry>
<cfelse>
<!--- cache exists and is not expired, so use it --->
<cfreturn duplicate(application.cepCache[arguments.cep]) />
</cfif>
</cfif>
<!---
<!--- Redirect to page two of the sign up process --->
<cfset redirectTo(controller="assine", action="perfil")>
--->
</cffunction>
Notice that I commented out the redirect you had at the end. That's because with my function, you'll be returning a value, and the redirect should be done after that, like so:
<cfset session.addressData = getAddress("some-CEP-value") />
<cfset redirectTo(controller="assine", action="perfil")>
If you're going to leave out the caching (As you say in a comment you will), then here is a version that makes no attempt at caching:
<cffunction name="getAddress" access="remote" returntype="any" output="false">
<cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) --->
<cfset var searchResult = "">
<cfset var nodes = "">
<cfset var cfhttp = "">
<cfset var stateid = 0 />
<cfset var tmp = structNew()>
<!--- Validate cep string --->
<cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8>
<!--- Consume webservice --->
<cftry>
<cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" />
<cfset searchResult = xmlparse(cfhttp.FileContent)>
<cfset nodes = xmlSearch(searchResult, "//tbCEP")>
<!--- If result insert address data into session struct --->
<cfif arrayLen(nodes)>
<cfset tmp.streetType = nodes[1].logradouro.XmlText />
<cfset tmp.streetName = nodes[1].nome.XmlText />
<cfset tmp.area = nodes[1].bairro.XmlText />
<cfset tmp.city = nodes[1].cidade.XmlText />
<cfset tmp.state = nodes[1].uf.XmlText />
<cfset tmp.cep = arguments.cep />
<!--- Get state id and add to struct --->
<cfset stateid = model("state").findOneByStateInitials(session.addressData.state)>
<cfset tmp.stateid = stateid.id />
</cfif>
<cfreturn duplicate(tmp) />
<!--- Display error if any --->
<cfcatch type="any">
<h3>Sorry, but there was an error.</h3>
<p>#cfcatch.message#</p>
</cfcatch>
</cftry>
</cfif>
<!---
<!--- Redirect to page two of the sign up process --->
<cfset redirectTo(controller="assine", action="perfil")>
--->
</cffunction>
Note that I did leave in the use of duplicate(). What this does is return a duplicate of the object (in this case, the struct). This is much more important when you start to work on applications where you're passing complex values into and out of functions over and over again. Using duplicate() causes things to be passed by value instead of by reference. It may not bite you in this case, but it's a good habit to get into.
I would also still use the function argument and return a value -- but it's arguable that this is my personal preference. In a way it is. I believe that a function should be fully encapsulated; a total "black box". You give it some input and it gives you back some output. It should not modify anything outside of itself. (Again, just my opinion.)
So assuming you're using this function as part of a larger multi-step process, you should still use it the same way I've described above. The only difference is that you're setting the session variable outside of the function body. Just as previously:
<cfset session.addressData = getAddress("some-CEP-value") />
<cfset redirectTo(controller="assine", action="perfil")>
That looks pretty straightforward. CF doesn't (yet?) have any magical XML-to-Query functions, but that would be pretty cool. If you wanted, you could probably write up an XSL transform to go from XML to WDDX so that you could use the cfwddx tag ... but that's probably putting the cart before the horse.
You need to move your arrayLen() if block into the try block. As it stands, if the cfhttp tag throws an error, the nodes variable will be a string and not an array, thus causing the arrayLen() to throw another error.
Minor nitpick: I wouldn't add a row to the query until inside the arrayLen() block. That way, the calling code can check recordCount to see if the result was a success.
Beyond that ... that's pretty much how it's done.

Can a ColdFusion cfc method determine its own name?

I am creating an API, and within each method I make a call to a logging method for auditing and troubleshooting. Something like:
<cffunction name="isUsernameAvailable">
<cfset logAccess(request.userid,"isUsernameAvailable")>
......
</cffunction>
I'd like to avoid manually repeating the method name. Is there a way to programatically determine it?
I've looked at GetMetaData() but it only returns info about the component (including all the methods) but nothing about which method is currently being called.
So now 3 ways.
If you are using ColdFusion 9.0 or higher there is now a function named GetFunctionCalledName(). It will return what you are looking for.
http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WS7cc222be8a31a47d-6e8b7083122cebfc8f2-8000.html
OR
Use ColdSpring and Aspect Oriented Programming (http://www.coldspringframework.org/coldspring/examples/quickstart/index.cfm?page=aop) to handle this for you.
OR
Use a cfthrow to generate a stack trace that has the information for you:
<cffunction name="determineFunction" output="FALSE" access="public" returntype="string" hint="" >
<cfset var functionName ="" />
<cfset var i = 0 />
<cfset var stackTraceArray = "" />
<cftry>
<cfthrow />
<cfcatch type="any">
<cfset stacktraceArray = ListToArray(Replace(cfcatch.stacktrace, "at ", " | ", "All"), "|") />
<!---Rip the right rows out of the stacktrace --->
<cfloop index ="i" to="1" from="#ArrayLen(stackTraceArray)#" step="-1">
<cfif not findNoCase("runFunction", stackTraceArray[i]) or FindNoCase("determineFunction", stackTraceArray[i])>
<cfset arrayDeleteAt(stackTraceArray, i) />
</cfif>
</cfloop>
<!---Whittle down the string to the func name --->
<cfset functionName =GetToken(stacktraceArray[1], 1, ".") />
<cfset functionName =GetToken(functionName, 2, "$")/>
<cfset functionName =ReplaceNoCase(functionName, "func", "", "once")/>
<cfreturn functionName />
</cfcatch>
</cftry></cffunction>
My recommendation would be use getFunctionCalledName, or if not on CF 9 ColdSpring, as it will probably buy you some other things.
I agree w/ tpryan. ColdSpring makes this very easy. However, here is another alternative. Instead of parsing the stack trace, you can parse the CFC file itself.
<cffunction name="foo" displayname="foo" hint="this is just a test function" access="public" returntype="string">
<cfset var test = getFunctionName(getMetaData().path, getPageContext().getCurrentLineNo()) />
<cfreturn test />
</cffunction>
<cffunction name="getFunctionName" hint="returns the function name based on the line number" access="public" returntype="string">
<cfargument name="filepath" type="string" required="true" />
<cfargument name="linenum" type="any" required="true" />
<cfset var line = "" />
<cfset var functionName = "" />
<cfset var i = 1 />
<!---- loop over CFC by line ---->
<cfloop file="#ARGUMENTS.filepath#" index="line">
<cfif findNoCase('cffunction', line, 1)>
<cfset functionName = line />
</cfif>
<cfif i EQ ARGUMENTS.linenum><cfbreak /></cfif>
<cfset i++ />
</cfloop>
<!---- parse function name ---->
<cfset functionName = REMatchNoCase("(\bname=[""|'])+[a-z]*[""|']", functionName) />
<cfset functionName = REMatchNoCase("[""']+[a-z]*[""']", functionName[1]) />
<cfset functionName = ReReplaceNoCase(functionName[1], "[""']", "", "all") />
<!---- return success ---->
<cfreturn functionName />
</cffunction>
The above is written for ColdFusion 8. CFLOOP added support for looping over files line by line (and doesn't read the entire file into memory). I did a few tests comparing the stack trace method vs. file parsing. Both performed equally well on a small CFC being called directly from a single CFM template. Obviously if you have very large CFCs the parsing method might be a bit slower. On the other hand, if you have a large stack trace (like if you are using any of the popular frameworks) then file parsing may be faster.
-= Viva ColdFusion =-
Well you might try this:
<cffunction name="getFunctionName" returntype="any">
<cfset meta =getMetaData(this)>
<cfreturn meta.functions[numberOfFunction].name>
</cffunction>
I've tried various things, and this is not accurate as the functions seem to be added to the array of functions in reverse alphabetical order. This seems limiting (and not solving the problem). I would imagine some native java code could be invoked, but i'm going to need to look into that.
This and This look like interesting reading on related internal functions.
Re: The other answer on coldspring. I found this in depth article on function metadata with coldspring.
Related question : How to get the name of the component that’s extending mine in ColdFusion?
I thought of another way that could work.
Setup an OnMissingMethod something like this:
<cffunction name="onMissingMethod">
<cfargument name="missingMethodName" type="string">
<cfargument name="missingMethodNameArguments" type="struct">
<cfset var tmpReturn = "">
<cfset var functionToCallName = "Hidden" & Arguments.missingMethodName>
<cfset arguments.missingMethodArguments.calledMethodName = Arguments.missingMethodName>
<cfinvoke method="#functionToCallName#" argumentcollection="#Arguments.missingMethodArguments#" returnvariable="tmpReturn" />
<cfreturn tmpReturn>
</cffunction>
Then name each of the regular methods with a prefix ("Hidden" in this example), and mark them as private. So my initial example would become:
<cffunction name="HiddenisUsernameAvailable" access="private">
<cfset logAccess(request.userid,Arguments.calledMethodName)>
......
</cffunction>
Now all the calls will be intercepted by onMissingMethod, which will add the method name to the arguments that get passed to the real method.
The downsides I see to this are that introspection no longer works properly, and you must be using named arguments to call all your functions. If you are not using named arguments, the args will randomly change order in the missingMethodNameArguments structure.
getFunctionCalledName() gives you the name of the active method.