Adobe ColdFusion makes writing web services a very simple task. In general you write a standard Coldfusion component (CFC), set it’s access type to “remote” and set output to “false”:

<cfcomponent>
    <cffunction name="getMonthAbbreviation" access="remote" output="false" returntype="string" hint="Returns the month abbreviation for a given month.">
       <cfargument name="mymonth" type="string" required="yes" hint="A full month name">

       <cfset var monthAbb = DateFormat(ARGUMENTS.mymonth & " 1, 2008","mmm")>

       <cfreturn monthAbb>
    </cffunction>
</cfcomponent>

That’s it. You have a web service. You can view it’s defintion (WSDL) by pointing your Web browser to: http://www.yoursite.com/youwebservice.cfc?wsdl. Depending on your browser you will see either the WSDL definition as XML or a blank page (do a view source). It should look something like (partial code below):

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://web services" xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://web services" xmlns:intf="http://web services" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns1="http://rpc.xml.coldfusion" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<!--WSDL created by ColdFusion version 8,0,1,195765-->
<wsdl:types>
<schema targetNamespace="http://rpc.xml.coldfusion" xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="CFCInvocationException">
<sequence/>
</complexType>
</schema>
</wsdl:types>
<wsdl:message name="getMonthAbbreviationResponse">
<wsdl:part name="getMonthAbbreviationReturn" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="getMonthAbbreviationRequest">
<wsdl:part name="mymonth" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="CFCInvocationException">
<wsdl:part name="fault" type="tns1:CFCInvocationException"/>
</wsdl:message>
<wsdl:portType name="example">
<wsdl:operation name="getMonthAbbreviation" parameterOrder="mymonth">
....

Now, if you want to call your web service in Coldfusion, start a new CFM page and add the following code:

<cfinvoke web service="http://www.yoursite.com/youweb service.cfc?wsdl" method="getMonthAbbreviation" returnVariable="myMonthAbb">
    <cfinvokeargument name="mymonth" value="December">
</cfinvoke>

<cfoutput>My abbreviated month is: #myMonthAbb#</cfoutput>

Something to mention here is that when you invoke a web service for the first time, Coldfusion will cache it behind the scenes. While this will make the initial call a little slower than normal (you probably will not notice), the purpose is to make all additional calls to that service faster. You can see cached web services listed in the Coldfusion Administrator.

While this is a nice feature, when you are developing the service, it can be a big pain. If you change the service, Coldfusion doesn’t always do a great job of refreshing the cached definition. Sometimes it will update the service, which is great. However, more often than not, you will need to open the CF Administrator tool and delete the service from the list, forcing CF to re-cache the updated version when you browse to it again. Even then, this technique doesn’t always work, leaving a CF restart as the only option to clear your web service definition.

Returning Complex Types Using CF Web Services

While Coldfusion makes it simple to return basic values like strings, numbers, or arrays via a web service, your end users may need more complex data returned. In Coldfusion, complex data is often passed around in CF structures. These are great “associative array” type data objects. Using a CFC as a standard component you can return a structure of data with no issue. This method also works via a web service, however the resulting XML looks more like a hash map than the nice structure design you may want.

In this example, I have added a new function to my web service called getMonthStruct (and yes I had to clear the web service cache in the CFAdmin to see the changes!). This method simply returns a struct of month names with their associated numeric value. As you can see the XML data returned in the soap envelope has key and value nodes nested in item nodes. While this works, it is not as clean as it could be.

For our example, what we really want is a list of nodes named the month name with the numeric value nested inside. To get this result we need to create a custom Coldfusion component and return that custom “object”.

To build a custom component, start a new CFC file. The CFC should have an opening component tag, and nested cfproperty tags for each piece of data you plan to return. That’s it. Following our months example, you would write a “Calendar.cfc” file and add properties like:

<cfcomponent>
    <cfproperty name="January" type="numeric" required="false">
    <cfproperty name="February" type="numeric" required="false">
    <cfproperty name="March" type="numeric" required="false">
    <cfproperty name="April" type="numeric" required="false">
    <cfproperty name="May" type="numeric" required="false">
    <cfproperty name="June" type="numeric" required="false">
    <cfproperty name="July" type="numeric" required="false">
    <cfproperty name="August" type="numeric" required="false">
    <cfproperty name="September" type="numeric" required="false">
    <cfproperty name="October" type="numeric" required="false">
    <cfproperty name="November" type="numeric" required="false">
    <cfproperty name="December" type="numeric" required="false">
</cfcomponent>

Keep in mind this isn’t the best example. Building a “person” CFC might make more sense, where the properties are things like Name, Address, City, State, Zip, Phone, etc. The properties can be of any standard type (Date, Array, Boolean, String, etc) as long as your main CFC web service can set them correctly. Keep in mind that this is just a component definition. This is not the CFC your end user will call. They will still call the original service from above, after we modify it like so:

<cfcomponent>
    <cffunction name="getMonthAbbreviation" access="remote" output="false" returntype="string" hint="Returns the month abbreviation for a given month.">
       <cfargument name="mymonth" type="string" required="yes" hint="A full month name">

       <cfset var monthAbb = DateFormat(ARGUMENTS.mymonth & " 1, 2008","mmm")>

       <cfreturn monthAbb>
    </cffunction>

    <cffunction name="getMonthStruct" access="remote" output="false" returntype="struct" hint="Returns a struct of months">
       <cfset var myCal = "">
       <cfset var monthName = "">

       <cfloop from="1" to="12" index="m">
          <cfset monthName = DateFormat(m & "/1/2008","mmmm")>
          <cfset StructInsert(myStruct,monthname,m)>
       </cfloop>

       <cfreturn myStruct>
    </cffunction>

    <cffunction name="getMonthObject" access="remote" output="false" returntype="Calendar" hint="Returns a object of months">
       <cfset var myCal = "">

       <cfobject component="Calendar" name="myCal">
         <cfset myCal.January = 1>
         <cfset myCal.February = 2>
         <cfset myCal.March = 3>
         <cfset myCal.April = 4>
         <cfset myCal.May = 5>
         <cfset myCal.June = 6>
         <cfset myCal.July = 7>
         <cfset myCal.August = 8>
         <cfset myCal.September = 9>
         <cfset myCal.October = 10>
         <cfset myCal.November = 11>
         <cfset myCal.December = 12>

       <cfreturn myCal>
    </cffunction>
</cfcomponent>

I left in the first “months” method, getMonthStruct(), so you could see how I generated the initial structure. However, we want to focus on the last method, getMonthObject(). It’s not as dynamic as the original method, but the point is to show the basics of the process. In getMonthObject() I made the following changes:

  • I set the returntype value to “Calendar” which is the name of my custom CFC. If you leave this as struct, you’ll still get the hash map XML code that you don’t really want.
  • I created a new var named myCal. While this is good form, if this were a normal CFC, you could skip that part and CF would still return a result. However, when using the CFC as a web service, skipping this step can cause errors generating the WSDL properly. EVERY variable needs to be declared at the top of your method, including loop indexes.
  • Next I create an object based off my Calendar.cfc definition and name it that var we just created (myCal).
  • Finally, just populate the values and return the custom object

Now when you invoke your web service using Coldfusion, you will get an object definition returned. The values are there, but you would need to then call the “get” methods from this object to get the values. However, our XML generated in the Soap envelope is much simpler and in the format that we want:

Your main web service code can of course be a lot more complicated than what I have here. As long as you have the basic parts you should get similar results. Some other tweaks you can make include:

  • If the data you are loading into your custom component contains any “null” values, you will see a closed XML node and it will have a “nill” attribute. If you prefer not to see the “nill” part, you can have your CF code check the value and if it is null you can set the value to empty string (“”). That will result in a simple closed XML node.
  • If you wanted to return multiple “Calendar” objects, set your method’s return type to “array”. Then run a loop that creates a Calendar object, loads its data, then pushes it into your array. Again, be sure to set a var at the top of your method for your array index () to avoid any issues.
  • There may be times when you want to nest a collection of one custom object type inside another. For example, say you return a restaurant “Menu” object. Inside that object is an attribute called “soups” and you want “soups” to be a collection of “Soup” objects. First defined custom Soup.cfc and Menu.cfc objects just like we did above with Calendar. In the Menu.cfc file, when you set the property for soups, you will set it like:
     <cfproperty name="soups" type="Soup[]" required="true">

:The property’s type is set to your custom component and a left and right square bracket. That is telling Coldfusion to expect to get an array of “Soup” objects for this value. To populate this value, you will follow the steps in the previous bullet to create an array of custom objects, then assign that array as the value:

    <!--- Assume we have an array of Soup objects called mySoupsArray --->

    <cfobject component="Menu" name="myMenu">
    <cfset myMenu.soups = mySoupsArray >

Using the SoapUI Tool to Test Your Web Services

While you can invoke a web service with Coldfusion, it’s more for consuming other people’s services into your code. It doesn’t really provide a good picture of what your end user’s will recieve from your service, especially if they are using another language or technology. Enter the SoapUI tool by Eviware. The features of this tool include:

  • It’s FREE (my favorite feature)
  • Comes as a standalone Java application or as an Eclipse plug-in
  • Allows you to create multiple projects, each having one or more web services
  • SoapUI consumes your service(s) and automatically loads all the methods and a default request for each
  • If the service has arguments, it automatically generates the XML syntax for you to populate with values
  • Includes several logs (soapui log, http log, jetty log, and an error log) that can help you if there is an error
  • Provides “Request Properties” to pass things like basic authentication information, timeout settings, and much more

« »