Wednesday, June 27, 2012

Weird thing in for loop in ColdFusion

I was making a simple XML iteration in script using a for loop, but it only returned one element in stead of all the elements. In a further inspection I discovered a typo which was not reported by ColdFusion.
Here is the for loop:

for (sCount=1; sCount<+arrayLen(xmlSomething["anotherElement"]); sCount++){

}

The problem is the <+ operator which should be <=
But no error report, just the first element that is returned. I wonder why!

Monday, June 18, 2012

Using different axis types in webservices (like unsigned int)

This is just a reminder for myself:
I read a great article about using the different datatypes that are not supported in ColdFusion (and not through JavaCast()). The way to go is to use the underlying axis object:
duration = CreateObject("java", "org.apache.axis.types.UnsignedInt").init(60);
In the example the variable "duration" is of type "UnsignedInt" and has the value 60. You can also do this in XML (see my previous post), but this is more elegant in my opinion.

Friday, June 15, 2012

Question: how to throw an error in a ColdFusion web service?

I've been trying to solve this promblem for some time now, but it seems there is no simple solution for this:
When you look at your webservice WSDL you have three operation parts: input, output and fault. The fault type of a generated ColdFusion WSDL is "CFCInvocationException". This is fine for most purposes, but not in my case. For example: I want to validate the input of the web service call to see if everything is correct before executing the method. Let's say you have a service to order something, then you want to authenticate the user, check the amount, and see if the product is available. If something is not correct then I want to return a fault.
In the service you can set the HTTP status code to 500 by using getPageContext().getResponse().setstatus(500);
But I still want to return a fault code and a fault message. If I generate this and return it in the body it will be returned in the SOAP output part, and not in de fault part. If I use "throw" to return the error, then it will be added to the "CFCInvocationException" and not my own fault type.
throw (message="some",type="urn:errorType",errorCode="999");

Does anyone have a solution to return a valid custom web service error (that is defined in the service WSDL) that will not be overrided by the ColdFusion fault handler?

UPDATE:
I haven't yet looked into CF11 so I don't know if this is fixed in that version, but I read a solution which isn't very elegant, but works. In short: you have to give the webservices their own application.cfc and use the onError function to catch the ColdFusion generated exception and replace the content by soap XML. A sample is described here: http://laksmatee.blogspot.nl/2014/02/half-baked-soap-support-in-coldfusion-9.html

Returning a complex object with specfic data types in a ColdFusion web service

Here is a folow-up on my previous post about returing an integer in a webservice. Most webservices return more then one value. In this example I will return a structure with a firstname and lastname:

<cfcomponent output="false">
   <cffunction name="giveMeAPerson" output="false" access="remote" returnType="person">
      <cfset var stReturn = createObject("component","person") />
      <cfset stReturn.firstName = "John" />
      <cfset stReturn.lastName = "Doe" />
      <cfset stReturn.age = "30" />
      <cfreturn stReturn />
   </cffunction>
</cfcomponent>

The returnType="person" points to person.cfc which is a value object with three properties: firstName, lastName and age with the according data type (string and numeric). (If the person.cfc is in a different folder than the web service cfc, then you must use the dot delimited path in the returnType field.)

If you want to use a specific data type for every struct key you will have to use XML. If you want the web service consumers to be able to fully use the different data types you have to specify the data types in the WSDL file. Save the generated WSDL to a file and point to this file in your component (see my previous post for the details on this). Next, edit the WSDL where the returnType is specified. Here I will change the data type "double" of the property "age" to "int". Next I will set the returntype of the function "giveMeAPerson" to "any".
Now it is time to construct the XML that has to be returned:

var XmlResponse = XmlNew();
XmlResponse.xmlRoot = XmlElemNew(XmlResponse,"urn:pointerToURN","giveMeAPersonResponse");
XmlResponse.xmlRoot.XmlAttributes['xsi:type'] = "giveMeAPersonResponse";
XmlResponse.xmlRoot.XmlChildren[1] = XmlElemNew(XmlResponse,"urn:customUrn","firstName");
XmlResponse.xmlRoot.XmlChildren[2] = XmlElemNew(XmlResponse,"lastName");
XmlResponse.xmlRoot.XmlChildren[3] = XmlElemNew(XmlResponse,"age");
XmlResponse.xmlRoot.firstName.XmlAttributes['xsi:type'] = "firstname";
XmlResponse.xmlRoot.lastname.XmlAttributes['xsi:type'] = "xsi:string";
XmlResponse.xmlRoot.age.XmlAttributes['xsi:type'] = "xsi:int";
XmlResponse.xmlRoot. firstName .XmlText = "John";
XmlResponse.xmlRoot.lastname.XmlText = "Doe"; 
XmlResponse.xmlRoot.age = JavaCast("int",30);

Then return the XmlResponse.

Note: In this example I used a custom data type for firstName. I did this to show you what to change to implement this. The custom data type must be defined in the WSDL under the given URN.
The conversion to int and string is not necessary, if you describe the type in the WSDL then ColdFusion will do the conversion.


Returning an integer (or other data types) with a ColdFusion web service

ColdFusion is a webserver language which is easy to learn and makes it easy to quickly achieve complex operations. This is all very nice, but can get in the way if you want to do something in a specific way. This problem arises when you have to deliver a webservice that returns a specific data type.
Here is a webservice which returns a number:
<cfcomponent output="false">
   <cffunction name="giveMeANumber" output="false" access="remote" returnType="numeric">
      <cfreturn RandRange(0,10) />
   </cffunction>
</cfcomponent>

If you call the web service in a different language than ColdFusion, or in a specific web service tool (like SoapUI) you will see that the returned value will be something like "5.0". This will be no problem for ColdFusion, but this may come as a problem for PHP or .Net. That's why I want the service to return "5" in stead of "5.0".
If you look at the webservice WSDL you will see that the webservice returns a double:

<wsdl:message name="giveMeANumberResponse">

<wsdl:part name="giveMeANumberReturn" type="xsd:double"/>
</wsdl:message>

Save the whole WSDL content to a text file and be sure that it is XML compliant (ie. no leading spaces before the XML tag). Now you can change the data type of the return value:

<wsdl:message name="giveMeANumberResponse">

<wsdl:part name="giveMeANumberReturn" type="xsd:int"/>
</wsdl:message>

Next, change the component tag to use the external wsdlfile:
<cfcomponent wsdlfile="myExternalwsdlfile.wsdl" style="document" output="false">
If you point to an external WSDL file you have to set style="document".

Next, set the returntype of the function to "any", this is not required for int (double or int are both numeric), but it is best practice because it will be required for other data type.
<cffunction name="giveMeANumber" output="false" access="remote" returnType="any">

Next, use Javacast around the return value to set the right data type, in this case: <cfreturn JavaCast("int",RandRange(0,10)) />  

To see which data type in ColdFusion will be returned with which data type (and if it may cause problems, see the ColdFusion reference "Data conversions between ColdFusion and WSDL data types".