Creating Web Services
Creating Web services that can be consumed by different platforms allows ColdFusion to communicate with a client over the Internet. The resulting Web service can expose internal information to the rest of a company's platforms or to platforms from a partner company that can communicate via the protocols we have previously discussed.In ColdFusion MX, we create Web services using ColdFusion Components. We can deploy a prebuilt CFC as a Web service or we can create a CFC specifically for the purpose of deploying it as a Web service. Either way, in order to create and deploy Web services we need to understand the basics of CFCs and how they operate.Chapter 19, "Creating Advanced ColdFusion Components."
Components
By now we hope you have found the time to read up on and experiment with ColdFusion Components (CFCs), which take an objectlike approach to grouping related functions and encapsulating business logic. If you have any experience with CFCs, you should have no trouble following the relatively simple examples in this section. Let's first review Listing 24.6, which contains the SimpleCreditRating.cfc, so we can understand how this ordinary ColdFusion Component can become a powerful Web service.
Listing 24.6. Web Service with a Simple Data Type (SimpleCreditRating.cfc)
The SimpleCreditRating CFC starts with a <cfcomponent> tag, which wraps the component's content. Then the <cffunction> tag with a name and return type defines a single function that retrieves the credit rating for a passed Social Security number. The optional access attribute is set to remote, which exposes this CFC to the world as a Web service. The getCreditRating function takes a single argument named SSN, which is of type string and is required. We create a credit rating by simply using the randRange() function. (In practice, there would be some real logic here.) The credit rating is then returned to the Web service client.NOTEThe <cffunction> attribute Required is ignored when a CFC is called as a Web service. For a Web service, all arguments are required. Because ColdFusion MX doesn't support method overloading, you need to define different method names for all possible parameter combinations. These methods can call a private function within the CFC that does the processing and allows for defaults.Now we have a simple CFC-based Web service, which we can publish and allow to be called by Web service clients across the Web. Those clients looking to find someone's credit rating need only have that person's Social Security number. Let's examine the ColdFusion MXgenerated WSDL for the Web service in Listing 24.7.
<!---
DATE: 06/01/02
AUTHOR: Brendan O'Hara (bohara@etechsolutions.com)
WEB SERVICE: SimpleCreditRating.cfc
DESCRIPTION: ColdFusion CFC deployed as a Web Service to return
a Credit Rating "string" for a passed-in Social Security number
which is a string represented by the argument "SSN".
--->
<cfcomponent output="false">
<!--- We define the CFC's single function that retrieves the credit
rating for a passed-in Social Security number and returns it --->
<cffunction name="getCreditRating" returnType="string" output="false"
access="remote">
<!--- The GetCreditRating function takes a single
argument SSN of type string, which is required --->
<cfargument name="SSN" type="string" required="yes">
<!--- var scope the result --->
<cfset var result = ">
<!--- This is where the logic would normally go. --->
<cfset result = randRange(500,900)>
<!--- Then the CreditRating is returned --->
<cfreturn result>
</cffunction>
</cfcomponent>
Listing 24.7. WSDL Display with Simple Data Type (SimpleCreditRating.cfc)
Now when we look at this WSDL document, it should quickly be apparent what is important. On the line with the first <wsdl:operation> tag, we see the operation getCreditRating, which is the method clients will wish to invoke. On the next line we see that the input message is getCreditRatingRequest, which is displayed on the first line with a <wsdl:message> tag. It has a single <wsdl:part> tag named SSN, which is of data type xsd:string. The message getCreditRatingResponse describes the return variable and its data type. Figure 24.6 shows the CFC.
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://c24.cfadv/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://c24.cfadv/"
xmlns:intf="http://c24.cfadv/"
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 Macromedia ColdFusion MX version 7,0,0,89494-->
<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="getCreditRatingResponse">
<wsdl:part name="getCreditRatingReturn" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="getCreditRatingRequest">
<wsdl:part name="SSN" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="CFCInvocationException">
<wsdl:part name="fault" type="tns1:CFCInvocationException"/>
</wsdl:message>
<wsdl:portType name="SimpleCreditRating">
<wsdl:operation name="getCreditRating" parameterOrder="SSN">
<wsdl:input message="impl:getCreditRatingRequest"
name="getCreditRatingRequest"/>
<wsdl:output message="impl:getCreditRatingResponse"
name="getCreditRatingResponse"/>
<wsdl:fault message="impl:CFCInvocationException"
name="CFCInvocationException"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="SimpleCreditRating.cfcSoapBinding"
type="impl:SimpleCreditRating">
<wsdlsoap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getCreditRating">
<wsdlsoap:operation soapAction="/>
<wsdl:input name="getCreditRatingRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:input>
<wsdl:output name="getCreditRatingResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:output>
<wsdl:fault name="CFCInvocationException">
<wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
name="CFCInvocationException"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="SimpleCreditRatingService">
<wsdl:port binding="impl:SimpleCreditRating.cfcSoapBinding"
name="SimpleCreditRating.cfc">
<wsdlsoap:address
location="http://127.0.0.1:8501/cfadv/c24/SimpleCreditRating.cfc"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Figure 24.6. CFC Explorer SimpleCreditRating.cfc Web service.
[View full size image]

Listing 24.8. Invocation Example with Simple Data Type (TestSimpleCreditRating.cfm)
Most of this script simply creates the theURL variable. This allows the script to run on any Web server and any directory. The script assumes that it lies in the same directory as the Web service. Once the URL is figured out, the script simply uses the <cfinvoke> tag to call it.Now let's take a look at a similar CFC that takes a struct data type. While a struct is similar to a number of data types in C++ and Java, it does not exactly match any of those defined in the XML Schema used by WSDL and SOAP for data-type representation and conversion. Listing 24.9 shows the Credit Rating Web Service, which for its only argument takes a map or ColdFusion structure as the data type for its only argument.
<!---
DATE: 06/01/02
AUTHOR: Brendan O'Hara (bohara@etechsolutions.com)
WEB SERVICE: TestSimpleCreditRating.cfm
DESCRIPTION: Test the CFC.
--->
<!--- Construct the URL dynamically --->
<cfif not findNoCase("https", cgi.server_protocol)>
<cfset theURL = "http://">
<cfelse>
<cfset theURL = "https://">
</cfif>
<!--- Add the server and current path --->
<cfset theURL = theURL & cgi.server_name & ":" & cgi.server_port &
cgi.script_name>
<!--- Now remove this file's name, which is at the end --->
<cfset theURL = listDeleteAt(theURL, listLen(theURL,"/"), "/")>
<cfinvoke webservice="#theURL#/SimpleCreditRating.cfc?wsdl"
method="getCreditRating" returnvariable="creditRating">
<cfinvokeargument name="SSN" value="000000001"/>
</cfinvoke>
<cfoutput>The result is: #creditRating#</cfoutput>
Listing 24.9. Web Service with struct or map Data Type (MapCreditRating.cfc)
Other than the data type change, the only thing new here is the fake logic used to return the credit rating. The problem we face is this: When clients other than ColdFusion call this Web service, they will need additional information to convert the arguments to a data type that CFMX expects and understands. Let's look at a portion of the generated WSDL for the mapCreditRating Web service to see what we are talking about. It is displayed in Listing 24.10.
<!--- DATE: 06/01/02
AUTHOR: Brendan O'Hara (bohara@etechsolutions.com)
WEB SERVICE: MapCreditRating.cfc
DESCRIPTION: ColdFusion CFC deployed as a Web Service to return
a Credit Rating string for a passed-in "Person" struct.
ARGUMENTS: name="Person" type="struct" required="yes"
--->
<cfcomponent output="false">
<!--- We define the CFC's single function that retrieves the credit
rating for a passed-in "Person" and returns it --->
<cffunction name="getCreditRating" output="false" returnType="string"
access="remote">
<!--- The getCreditRating function takes a single argument
called "Person" of type struct, which is required --->
<cfargument name="Person" type="struct" required="yes">
<!--- var scope the result --->
<cfset var result = ">
<!--- This would be real logic here --->
<cfset result = len(arguments.person.ssn) * randRange(50,100)>
<!--- Then the CreditRating is returned --->
<cfreturn result>
</cffunction>
</cfcomponent>
Listing 24.10. WSDL Portion with struct or map Data Type (MapCreditRating.cfc)
This map complex type is generated by all uses of the struct data type in <cffunction> arguments. You will notice that both the key and value can be of any data type. A call to this Web service will work if it comes from a ColdFusion page, but it's not the most platform-independent way of accepting structured data. Because ColdFusion doesn't predefine data types for all variables, we need to be aware of data types that may be problematic. The struct or map data type common to ColdFusion is not exactly represented in the XML Schema that SOAP uses for automatic data-type translation. Another unsupported data type is query. That is why we need to limit the use of unsupported data types in Web services when interacting with other platforms.
<wsdl:types>
<schema targetNamespace="http://xml.apache.org/xml-soap"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://c24.cfadv/"/>
<import namespace="http://rpc.xml.coldfusion/"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="mapItem">
<sequence>
<element name="key" nillable="true" type="xsd:anyType"/>
<element name="value" nillable="true" type="xsd:anyType"/>
</sequence>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="item"
type="apachesoap:mapItem"/>
</sequence>
</complexType>
</schema>
Defining Complex Data Types
Web services may be significantly more complex than our "simple" example, and their input parameters may be custom or unsupported data types. A Web service may need to accept multiple fields, or a single field containing a complex data type, in order to process the called function and return data to the caller. Object-oriented languages such as Java, C++, and C# have direct mappings from their complex data types to the XML Schema data types used by SOAP and WSDL. Unfortunately, ColdFusion doesn't have direct mappings to many of these complex data types. What it does have is the capacity to let you define your own complex data types using CFCs and the <cfproperty> tag.Listing 24.11 shows a CFC completely empty of content except for <cfproperty> tags.
Listing 24.11. Complex Data Type for Use with a Web Service (CreditPerson.cfc)
The <cfproperty> tag is used in order for Web services to define a complex data type. In ColdFusion this would be a structure, but because a struct is not a supported data type, we use another CFC without arguments to define the structure of our complex data type. The Credit Rating CFC Web Service using a complex data type is shown in Listing 24.12.
<!---
DATE: 06/01/02
AUTHOR: Brendan O'Hara (bohara@etechsolutions.com)
COMPONENT: CreditPerson.cfc
DESCRIPTION: ColdFusion CFC deployed as a complex data type for
use with Web Services. No functions. No arguments.
--->
<cfcomponent>
<cfproperty name="FirstName" type="string">
<cfproperty name="Lastname" type="string">
<cfproperty name="Address" type="string">
<cfproperty name="City" type="string">
<cfproperty name="State" type="string">
<cfproperty name="ZipCode" type="string">
<cfproperty name="SSN" type="string">
</cfcomponent>
Listing 24.12. Web Service with Complex Data Type (ComplexCreditRating.cfc)
When the type attribute in the <cfargument> tag is not a recognized type, the attribute is assumed to be the name of a ColdFusion Component. The CFC is converted to a complex data type when represented in WSDL. This will take extra work to extract and convert the data on the client side, so the attribute should be used only when it is clearly advantageous.Take a look at the generated WSDL, and notice that the complex type is represented differently than our struct and map exampleseven though the processing in the CFC Web service is virtually identical. Listing 24.13 shows the WSDL for our ComplexCreditRating Web service.
<!---
DATE: 06/01/02
AUTHOR: Brendan O'Hara (bohara@etechsolutions.com)
WEB SERVICE: ComplexCreditRating.cfc
DESCRIPTION: ColdFusion CFC deployed as a Web Service to return
a Credit Rating "string" for a passed-in "Person", which is a Complex
Data Type which is defined in the CFC Person.cfc.
ARGUMENTS: name="SSN" type="string" required="yes"
--->
<cfcomponent output="false">
<!--- We define the CFC's single function that retrieves the credit
rating for a passed-in "Person" and returns it --->
<cffunction name="GetCreditRating" returnType="string" output="false"
access="remote">
<!--- The GetCreditRating function takes a single argument
called "Person" of type struct, which is required --->
<cfargument name="person" type="CreditPerson" required="yes">
<!--- var scope the result --->
<cfset var result = ">
<!--- This would be real logic here --->
<cfset result = len(arguments.person.ssn) * randRange(50,100)>
<!--- Then the CreditRating is returned --->
<cfreturn result>
</cffunction>
</cfcomponent>
Listing 24.13. WSDL Display with Complex Data Type (ComplexCreditRating.cfc)
This example provides significantly more information than does the WSDL generated for our ColdFusion struct or map example. The map had undefined key values and an undefined number of elements in the map, so someone trying to call our Web service from only the WSDL would have no clue what parameters were required and what their true data types should be. Our custom complex data type, however, defines the elements in the structure and their associated types. In this case they are all strings, but they could just as easily have all been different. This Web service can work when called by a ColdFusion page, and with minimal adjustments and testing can be accessed by most other platforms. Figure 24.7 shows the CFC Explorer for the ComplexCreditRating Web service.
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://c24.cfadv/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:apachesoap="http://xml.apache.org/xml-soap"
xmlns:impl="http://c24.cfadv/"
xmlns:intf="http://c24.cfadv/"
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 Macromedia ColdFusion MX version 7,0,0,89494-->
<wsdl:types>
<schema targetNamespace="http://c24.cfadv/"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://rpc.xml.coldfusion/"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="creditperson">
<sequence>
<element name="SSN" nillable="true" type="xsd:string"/>
<element name="address" nillable="true" type="xsd:string"/>
<element name="city" nillable="true" type="xsd:string"/>
<element name="firstName" nillable="true" type="xsd:string"/>
<element name="lastname" nillable="true" type="xsd:string"/>
<element name="state" nillable="true" type="xsd:string"/>
<element name="zipCode" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</schema>
<schema targetNamespace="http://rpc.xml.coldfusion/"
xmlns="http://www.w3.org/2001/XMLSchema">
<import namespace="http://c24.cfadv/"/>
<import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
<complexType name="CFCInvocationException">
<sequence/>
</complexType>
</schema>
</wsdl:types>
<wsdl:message name="GetCreditRatingRequest">
<wsdl:part name="person" type="impl:creditperson"/>
</wsdl:message>
<wsdl:message name="GetCreditRatingResponse">
<wsdl:part name="GetCreditRatingReturn" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="CFCInvocationException">
<wsdl:part name="fault" type="tns1:CFCInvocationException"/>
</wsdl:message>
<wsdl:portType name="ComplexCreditRating">
<wsdl:operation name="GetCreditRating" parameterOrder="person">
<wsdl:input message="impl:GetCreditRatingRequest"
name="GetCreditRatingRequest"/>
<wsdl:output message="impl:GetCreditRatingResponse"
name="GetCreditRatingResponse"/>
<wsdl:fault message="impl:CFCInvocationException"
name="CFCInvocationException"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ComplexCreditRating.cfcSoapBinding"
type="impl:ComplexCreditRating">
<wsdlsoap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="GetCreditRating">
<wsdlsoap:operation soapAction="/>
<wsdl:input name="GetCreditRatingRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:input>
<wsdl:output name="GetCreditRatingResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:output>
<wsdl:fault name="CFCInvocationException">
<wsdlsoap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
name="CFCInvocationException"
namespace="http://c24.cfadv/" use="encoded"/>
</wsdl:fault>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ComplexCreditRatingService">
<wsdl:port binding="impl:ComplexCreditRating.cfcSoapBinding"
name="ComplexCreditRating.cfc">
<wsdlsoap:address location=
"http://127.0.0.1:8501/cfadv/c24/ComplexCreditRating.cfc"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Figure 24.7. CFC Explorer for ComplexCreditRating.cfc Web service.
[View full size image]

Figure 24.8. CFC Explorer for CreditPerson.cfc complex data type.
[View full size image]
