Search This Blog

Friday, September 5, 2014

ColdFusion: Using CFTHREAD

<cfthread>...</cfthread> was introduced in ColdFusion 8; and I had not got chance to use it until recently. Basically, cfthread is used for creating asynchronous/independent processes aside from page-level processing. Here are my notes from playing with the tag and reading CF documentations.

  • thread-local scope (local scope in thread): Define the variables with "var" or without specifying a scope name. It is best practice if we specify var to create thread-local-scope variables.
  • thread scope: Define the variables with prefix thread. followed by variableName. The thread scope also contain metadata such as elapsedTime, name, status, etc.
    To call thread-scope variables from page thread: threadName.variableName
    To call thread-scope variables from other thread: cfthread.threadName.variableName
  • attributes scope: Define via cfthread attribute (<cfthread variableName="value">) or attributeCollection
  • threadName.status: NOT_STARTED, RUNNING, TERMINATED, COMPLETED, WAITING
  • threadName.error: Exist when there is an error. Use structKeyExists(threadName, "error") to check an error.

Reference: Using thread data

Friday, August 29, 2014

ColdFusion: Using Implicit getters and setters

Usually, I wrote my own getters and setters. It's kind of habit since the introduction of <cfcomponent>...</cfcomponent>. However, it took time and made the code longer. So, I decided to try implicit getters and setters by setting accessors="true" and using <cfproperty />. There are two important things that I found:

  • We can override implicit getters and setters with our own. However, the implementation has to be both (getter and setter). It cannot be only either getter or setter only.
  • The getter and setter attribute in <cfproperty /> is to limit access if they can be called from inside only. Setting getter/setter to false mean the method cannot be called from outside the component. It does not mean to generate or not to generate getter/setter for that property.

AcmeService.cfc

<!--- Enable implicit getters and setters --->
<cfcomponent output="false" accessors="true">

  <!--- Override its getter and setter. See getDelay() and setDelay(). --->
  <cfproperty name="delay" type="numeric" />
  
  <!--- Use its implicit getter and setter  --->
  <cfproperty name="text" type="string" />
  
  <cffunction name="init" output="false">
    <!--- Set initial values --->
    <cfset THIS.setDelay(0) />
    <cfset THIS.setText("Hi again.") />

    <cfreturn THIS />
  </cffunction>

  <cffunction name="echo" output="false" hint="Echoes back whatever received.">
    <cfargument name="data" required="true" />

    <cfset sleep(THIS.getDelay()) />

    <cfreturn ARGUMENTS.data 
      & " This process was delayed for " & (THIS.getDelay() / 1000) & " seconds." 
      & " Another property with implicit getter/setter: " & THIS.getText() />
  </cffunction>

  <!--- While accessor="true" creates implicit getters/setters, we can override them.
    However, the implementation has to be one set (getter and setter).
    It cannot be only getter or setter. --->
  <cffunction name="getDelay" output="false" returntype="numeric">
    <cfreturn THIS.delay />
  </cffunction>
  <cffunction name="setDelay" output="false" returntype="void" hint="Sets delay in seconds.">
    <cfargument name="delay" type="numeric" required="true" />
    <cfset THIS.delay = ARGUMENTS.delay * 1000 />
  </cffunction>

</cfcomponent>

Reference: Implicit Get and Set Functions

Tuesday, April 8, 2014

Deciding Whether to Use SOAP or REST

Lately, I have been working on Web Service and API projects. During initial working process, I always thought whether I should use SOAP or REST. After done some research and learned by mistakes, I concluded that in most cases, SOAP is a better choice. Why? Because SOAP can handle complex operations more than just CRUD. REST, of course, can be used for non-CRUD operations. However, the use of HTTP method verbs creates barriers in the implementation.

Some useful links regarding SOAP:
SOAP Tutorial
How to Determine the SOAP Version of a Message
The hidden impact of WS-Addressing on SOAP

Wednesday, April 2, 2014

ColdFusion: Variables-scope behavior in Application.cfc

The existence of Application.cfc is a huge enhancement over Application.cfm. Whenever feasible, always use Application.cfc. It provides methods, such as onApplicationStart(), onRequestStart(), onSessionStart(), onCFCRequest(), and so on. Understanding how the Application.cfc works is a bit confusing, especially when you come from Application.cfm. In this blog, I'd like to focus on how variables-scope behaves on the requested page when onRequestStart() and onRequest() are implemented.

We all know that any variables-scope variables defined in Application.cfm are available on requested page. Now, how can we translate this behavior to Application.cfc? Here's the answer. Variables-scope variables should be defined in onRequest(). If we defined in onRequestStart(), it would work as well. However, the variables would not be available on request page until onRequest() is implemented. In other words, any variables-scope defined in Application.cfc live inside until onRequest() is implemented.

BONUS: When implementing Application.cfc's native methods, don't add output="false". Simply omit it, unless you know what you are doing.

  • Adding output="false" is equivalent to cfsilent, which suppresses contents generated by CF tags.
  • Adding output="true" is equivalent to cfoutput, which evaluates expression between pound signs; and outputs other contents.
  • Omitting the attribute outputs contents.

Reference: Defining the application and its event handlers in Application.cfc

Saturday, February 22, 2014

ColdFusion: Implement Authentication for Web Services Using SOAP Header

The following script demonstrates how to implement authentication in SOAP header using ColdFusion.
We could pass username and password in simple value, but in this example we wanted to deliver them in XML.

NOTE: This blog has been updated with new code. The original one was written two years ago. :)

WebService.cfc

<cfcomponent output="false" hint="An example of Web Service.">

  <cffunction name="echo" output="false" access="remote" returntype="string" hint="Accept text/string; and return it back to the caller.">
    <cfargument name="message" type="string" required="false" default="" />
    
    <cfset var response = "It is NOT a SOAP request." />
    <cfset var namespace = "http://localhost/test/soap/" />

    <!--- Check if it is a SOAP request and the request is legit --->
    <cfif isSOAPRequest() AND authenticateRequest()>
      <cfset response = "It is a SOAP request. Echoes: " & arguments.message />
    </cfif>

    <cfreturn response />
  </cffunction>

  <cffunction name="authenticateRequest" output="false" access="private" returntype="boolean" hint="Authenticate request.">
    <cfset var result = false />
    <cfset var namespace = "http://localhost/test/soap/" />

    <!--- Get authentication node and return it as an XML --->
    <cfset var auth_node = getSOAPRequestHeader(namespace, "authentication", true) />

    <!--- Parse the authentication node content, which is credential --->
    <cfset var cred_node = xmlParse(auth_node.xmlRoot.xmlText) />

    <!--- Get the data from the XML --->
    <cfset var username = cred_node.xmlRoot["username"].xmlText />
    <cfset var password = cred_node.xmlRoot["password"].xmlText />   

    <!--- Verify the username and password --->
    <cfif NOT compare("jsmith", username) AND NOT compare("abc123", password)>
      <cfset result = true />

    </cfif>

    <cfreturn result />
  </cffunction>

</cfcomponent>

client.cfm

<!--- Settings --->
<cfset end_point = "http://localhost/test/soap/WebService.cfc?WSDL" />
<cfset ws_args = {refreshwsdl=true} /> <!--- Force ColdFusion to refresh WSDL stub --->

<!--- Create Web service object --->
<cfset web_service = createObject("webService", end_point, ws_args) />

<!--- Construct XML document, which contains username and password --->
<cfxml variable="xml_doc">
  <credential>
    <username>jsmith</username>
    <password>abc123</password>
  </credential>
</cfxml>

<cfset namespace = "http://localhost/test/soap/" />

<!--- Add credential in the header --->
<cfset addSOAPRequestHeader(web_service, namespace, "authentication", toString(xml_doc)) />

<!--- Add SOAPAction:"" in the header --->
<!--- The header field value of empty string ("") means that the intent of the SOAP message is provided by the HTTP Request-URI. 
  No value means that there is no indication of the intent of the message. 
  Reference: http://www.w3.org/TR/2000/NOTE-SOAP-20000508/ --->
<cfset addSOAPRequestHeader(web_service, namespace, "SOAPAction", chr(34) & chr(34)) /> <!--- The value can be empty --->

<cfset message = "Hello, there!">
  
<!--- Invoke the service --->
<cfset response = web_service.advancedEcho(message) />

<!--- Get the response in SOAP --->
<cfdump var="#getSOAPResponse(web_service)#" />

ColdFusion: Half Baked SOAP Support in ColdFusion 9

I've been on and off working on SOAP using ColdFusion 9 (CF9) for a while. I found out that the support of SOAP was not fully implemented. For example, when a SOAP exception happens, CF9 throws SOAP fault in the form of regular ColdFusion exception. That's definitely not right! We should deliver the fault in SOAP format. Again, this broken support is happening on CF9. I didn't check on other CF versions.

To fix the situation, I wrote a process to intercept the CF exception, parse the SOAP fault, and deliver it in SOAP format. First, I utilize onError() in Application.cfc to intercept an exception. Second, there is a CFC to parse the fault that is in the exception.

NOTE: My code includes a user-defined function, arrayOfStructsSoft() from Nathan Dintenfass. Thanks, buddy!

Application.cfc

<cfcomponent output="false">
  <cfset this.name = "soap" />

  <!--- When error occurred, the SOAP throws a ColdFusion exception that contains text-based fault info.
    That's the reason we need to parse and return it as a SOAP XML.
    This method generates SOAP envelope that contains fault in the body. --->
  <cffunction name="onError" returntype="void">
    <cfargument name="exception" required="true" /> 
    <cfargument name="eventName" type="string" required="true" />

    <cfset var soap_envelope = "" />
    <cfset var soap_fault = createObject("component", "SoapFault").init(arguments.exception['detail']) />
    <cfset var fault_string = toString(soap_fault.getFaultNode("xml")) />

    <!--- Remove XML header --->
    <cfset fault_string = replaceNoCase(fault_string, '<?xml version="1.0" encoding="UTF-8"?>', '') />

    <!--- Replace <Fault /> with <soapenv:Fault /> to follow ColdFusion naming conventions --->
    <cfset fault_string = replaceNoCase(fault_string, '<Fault>', '<soapenv:Fault>') />
    <cfset fault_string = replaceNoCase(fault_string, '</Fault>', '</soapenv:Fault>') />

    <!--- Embed the fault to SOAP envelope --->
    <cfsavecontent variable="soap_envelope">
      <soapenv:Envelope 
        xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" <!--- SOAP 1.1 --->
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <soapenv:Body>
          <cfoutput>#fault_string#</cfoutput>
        </soapenv:Body>
      </soapenv:Envelope>
    </cfsavecontent>

    <!--- Output the result as a SOAP XML. 
      So, the caller can receive and digest an exception/fault in SOAP format instead of ColdFusion native format. --->
     <cfcontent type="text/xml; charset=utf-8" />
    <cfoutput>#xmlParse(soap_envelope)#</cfoutput> <!--- xmlParse() converts string to XML --->
  </cffunction>

</cfcomponent>

SoapFault.cfc

<cfcomponent output="false">

  <cfset variables.instance = {} />
  <cfset variables.instance["fault"] = {} />

  <cffunction name="init" output="false">
    <cfargument name="exception" type="string" required="false" default="" />

    <cfif len(arguments.exception)>
      <cfset variables.instance["fault"] = parseFault(arguments.exception) />
    </cfif>

    <cfreturn this />
  </cffunction>

  <cffunction name="parseFault" output="false" access="public" returntype="struct">
    <cfargument name="exception" type="string" required="true" /> <!--- Receive an exception string from ColdFusion that contains SOAP fault information --->

    <!--- REFERENCE: http://axis.apache.org/axis/java/apiDocs/org/apache/axis/AxisFault.html --->

    <!--- The order should be matched with the one that ColdFusion outputs --->
    <cfset var node_list = "faultCode,faultSubcode,faultString,faultActor,faultNode,faultDetail" />
    <cfset var node_pos1 = 0 />
    <cfset var node_pos2 = 0 />
    <cfset var node_name = "" />
    <cfset var node_array[1] = {element_name=node_name, element_position=node_pos1} />
    <cfset var fault_info_pos1 = 0 />
    <cfset var fault_info = "" />
    <cfset var fault_node = "" />
    <cfset var result = {} />
    <cfset var i = 1 />

    <cftry>
      <!--- Build array that contains the element name and its position --->
      <cfloop list="#node_list#" index="node_name">
        <!--- Get the start position of each element --->
        <cfset node_pos1 = findNoCase(node_name & ":", arguments.exception) />

        <!--- Build a list based on what it is found --->
        <cfif node_pos1>
          <!--- Build a struct and set it to array --->
          <cfset node_array[i] = {element_name=node_name, element_position=node_pos1} />
          <!--- Increment array index --->
          <cfset i = i + 1 />
        </cfif>
      </cfloop>

      <!--- Sort the structure --->
      <cfset node_array = arrayOfStructsSort(node_array, "element_position", "asc", "numeric") />

      <cfloop index="i" from="1" to="#arrayLen(node_array)#">
        
        <!--- Assume that all node in the list is found. In other words, no element_position is zero. --->
        <cfset node_pos1 = findNoCase(node_array[i].element_name, arguments.exception) />
        
        <!--- Assume that faultDetail is in the last list --->
        <cfif node_array[i].element_name NEQ "faultDetail">
          <!--- Get the end position and use the next element as a reference --->
          <cfset node_pos2 = findNoCase(node_array[i+1].element_name, arguments.exception, node_pos1) />
        
        <cfelse>
          <!--- For the last element, faultDetail, use </pre> as the reference
            The tag is found in cfcatch.detail --->
          <cfset node_pos2 = findNoCase("</pre>", arguments.exception, node_pos1) />

          <!--- If cannot find the tag, find "hostname:"
            "hostname:" is found in cfcatch.faultString --->
          <cfif NOT node_pos2>
            <cfset node_pos2 = findNoCase("hostname:", arguments.exception, node_pos1) />
          </cfif>
        </cfif>
        
        <!--- Get the whole string from "elementName:" to the next element --->        
        <cfset fault_node = mid(arguments.exception, node_pos1, node_pos2 - node_pos1) />

        <!--- Get position of the current element value --->
        <cfset fault_info_pos1 = find(":", fault_node) + 1 />

        <!--- Get the element value --->
        <cfset fault_info = trim( mid(fault_node, fault_info_pos1, len(fault_node) - fault_info_pos1 + 1) ) />

        <!--- Insert in to a struct --->
        <cfset result[node_array[i].element_name] = fault_info />

      </cfloop>

      <cfcatch type="any">
        <cfloop list="#node_list#" index="node_name">
          <cfif node_name EQ "faultString">
            <cfset result[node_name] = cfcatch.message />
            
          <cfelseif node_name EQ "faultDetail">
            <cfset result[node_name] = cfcatch.detail />

          <cfelse>
            <cfset result[node_name] = "" />

          </cfif>
        </cfloop>

      </cfcatch>

    </cftry>

    <cfreturn result />
  </cffunction>

  <cffunction name="getFaultNode" output="false" returntype="any">
    <cfargument name="return_type" type="string" required="false" default="struct" />

    <cfset var result = "" />
    <cfset var root = "" />

    <cfif arguments.return_type EQ "struct">
      <cfset result = duplicate(variables.instance["fault"]) />

    <cfelseif arguments.return_type EQ "xml">
      <cfset result = xmlNew() />

      <!--- Create a root element --->
      <cfset result.xmlRoot = xmlElemNew(result, "Fault") />
      <cfset root = result.xmlRoot />

      <!--- For each element, create and populate the element --->
      <cfloop collection="#variables.instance["fault"]#" item="element">
        <!--- According to http://schemas.xmlsoap.org/soap/envelope/, the element name should be all lower case --->
        <cfset arrayAppend(root.xmlChildren, xmlElemNew(result, lCase(element))) />
        <cfset root[element].xmlText = xmlFormat(variables.instance["fault"][element]) />
      </cfloop>

    </cfif>

    <cfreturn result />
  </cffunction>

  <cffunction name="getFaultCode" output="false" returntype="string">
    <cfreturn variables.instance["fault"]["faultCode"] />
  </cffunction>

  <cffunction name="getFaultString" output="false" returntype="string">
    <cfreturn variables.instance["fault"]["FaultString"] />
  </cffunction>

  <cffunction name="getFaultActor" output="false" returntype="string">
    <cfreturn variables.instance["fault"]["faultActor"] />
  </cffunction>

  <cffunction name="getFaultDetail" output="false" returntype="string">
    <cfreturn variables.instance["fault"]["faultDetail"] />
  </cffunction>

  <cfscript>
  /**
   * Sorts an array of structures based on a key in the structures.
   * 
   * @param aofS   Array of structures. (Required)
   * @param key    Key to sort by. (Required)
   * @param sortOrder    Order to sort by, asc or desc. (Optional)
   * @param sortType   Text, textnocase, or numeric. (Optional)
   * @param delim    Delimiter used for temporary data storage. Must not exist in data. Defaults to a period. (Optional)
   * @return Returns a sorted array. 
   * @author Nathan Dintenfass (nathan@changemedia.com) 
   * @version 1, April 4, 2013 
   */
  private function arrayOfStructsSort(aOfS,key){
      //by default we'll use an ascending sort
      var sortOrder = "asc";    
      //by default, we'll use a textnocase sort
      var sortType = "textnocase";
      //by default, use ascii character 30 as the delim
      var delim = ".";
      //make an array to hold the sort stuff
      var sortArray = arraynew(1);
      //make an array to return
      var returnArray = arraynew(1);
      //grab the number of elements in the array (used in the loops)
      var count = arrayLen(aOfS);
      //make a variable to use in the loop
      var ii = 1;
      //if there is a 3rd argument, set the sortOrder
      if(arraylen(arguments) GT 2)
        sortOrder = arguments[3];
      //if there is a 4th argument, set the sortType
      if(arraylen(arguments) GT 3)
        sortType = arguments[4];
      //if there is a 5th argument, set the delim
      if(arraylen(arguments) GT 4)
        delim = arguments[5];
      //loop over the array of structs, building the sortArray
      for(ii = 1; ii lte count; ii = ii + 1)
        sortArray[ii] = aOfS[ii][key] & delim & ii;
      //now sort the array
      arraySort(sortArray,sortType,sortOrder);
      //now build the return array
      for(ii = 1; ii lte count; ii = ii + 1)
        returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
      //return the array
      return returnArray;
  }
  </cfscript>

</cfcomponent>

Tuesday, October 29, 2013

ColdFusion: Useful Functions for Files and Directories/Folders

This blog entry is actually for myself. Sometimes, I get confused with the function names because the names are similar to each other. Below are the names and descriptions of their outputs.

<!--- Returns an absolute path from logical path (e.g., C:\inetpub\wwwroot\my_app\) --->
<cfoutput>#ExpandPath("\")#</cfoutput>

<!--- Returns the absolute path of the application base page (e.g., C:\inetpub\wwwroot\my_app\index.cfm) --->
<cfoutput>#GetBaseTemplatePath()#</cfoutput>

<!--- Returns the absolute path of the page (e.g., C:\inetpub\wwwroot\my_app\views\main\default.cfm) --->
<cfoutput>#GetCurrentTemplatePath()#</cfoutput>

<!--- Returns the directory from an absolute path (e.g., C:\inetpub\wwwroot\my_app\views\main\) --->
<cfoutput>#GetDirectoryFromPath(GetCurrentTemplatePath())#</cfoutput>

<!--- Returns the filename including the extension from an absolute path (e.g., default.cfm) --->
<cfoutput>#GetFileFromPath(GetCurrentTemplatePath())#</cfoutput>
<br />