Wednesday, December 29, 2004

ColdFusion Webservice Issues

ColdFusion has been giving me fits for the last few days. I have been trying to publish a webservice using ColdFusion 6.1. At MAX 2003, they made it seem so simple.

Here are the problems that I encountered, and their solutions. I sure wish that these would have been documented somewhere. Incidentally, I am running ColdFusion MX 6.1, the full developer version, using the builtin webserver, on Windows XP Professional

Problems with changing the returnType
When I first started playing with the cffunction access="remote", everything seemed to be working OK. I did a simple web service with returnType="string", and which returned the following string: "test_string". Everything worked great. However, when I played with changing it to returnType="numeric", and tried to return 123456, I immediately started getting a rather bizarre error that looked something like this:

Could not perform web service invocation "test_function" because AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode: faultString: org.xml.sax.SAXException: Bad types (class
java.lang.String -> class [Ljava.lang.Object;) faultActor: faultNode:
faultDetail: {http://xml.apache.org/axis/}stackTrace: blah,blah,blah....

This one took me a day and a half to figure out. Finally after a couple of hours on google, I learned the following. Turns out that when you change a component, you aren't necessarily changing the WSDL. ColdFusion creates the WSDL the first time that a component function is called as a web service, and then caches it away for later use. So, when you change your component, especially if you change the returnType, the WSDL and the way that the web service behaves no longer match. I don't know how long ColdFusion keeps this cache, but it is long enough to generate errors for several hours/days.

What you can do to alleviate this problem is to log into the ColdFusion Administrator, click on "Web Services" (under "Data & Services") , find the webservice that you changed, and click the "refresh" icon listed to the left of it. Doing so will refresh the WSDL so that you don't have to get this rather nasty error any more.

Problems with <cfinvoke>
I was never able to successfully consume the web service using cfinvoke. I don't know if it was a problem with the function that I was calling, or with the way that cfinvoke is implemented, but I never did get it to work right. However, I did have success using the following:

<cfset object_name = createObject("webservice","http://[path]/[component].cfc?wsdl")>
<cfset foo = object_name.test_function()>
<cfdump var="#foo#">

Problem with returnType="array"
I finally got the web service component publishing with returnType="string" and returnType="numeric". However, trying to get it to work with returnType="array" was giving me fits. Then after learning about the Administrator refresh, I finally started getting the following error:
java.lang.NullPointerException

I never actually did find an answer to this one on google, but I was able to figure it out. When ColdFusion creates an array, it is not just memory addresses like a simple array in C++. It is actually creating an array object that the variable name points to. When I was returning the array, it was just returning the pointer. Then after returning the pointer, it would destruct the webservice invocation, thus destructing the array, and the pointer was left pointing to nothing. So, I made the following change to my cfreturn, and now everything works just great (I wish that this would have been documented somewhere in big bold letters!):

Before
<cfreturn variables.test_array>

After
<cfreturn duplicate(variables.test_array)>

Adding the "duplicate" changes the way that ColdFusion returns the variable.

This is a very important concept to understand. When you copy a complex variable from one place to another in ColdFusion, it doesn't actually copy the objects that the variable points to. It only copies the pointer. For example, let variables.test_array be an arbitrary array. Remember from before that variables.test_array isn't the actual array, it is only a pointer to an array. When you make the following assignment:

<cfset array_copy=variables.test_array>

you aren't actually copying the array. You are only copying the pointer to the array. Thus, if you make any changes to variables.array_copy, you also are making changes to variables.test_array; they are the same array or in other words, they are two names for the same thing. This is called a shallow copy.

However, if you want to make an actual seperate copy of the array, you have to use the "duplicate" function. Example:

<cfset array_copy=duplicate(variables.test_array)>

In the above example, you made an seperate copy of the array. This is called a deep copy. It actually goes through, and makes a copy of the array pointed to by variables.test_array, and all objects pointed to by variables.test_array.

CFCInvocationException
For some reason, the in the XML of the WSDL document, the following appears in several places:

CFCInvocationException

What does this mean? Can't say for certain. I think that it is a generic exception name used by ColdFusion to return debugging information in the event of an error.

5 comments:

Robert said...

Thanks a million for the webservice array return solution. I don't know how many searches I've done try to fix this. I wonder why adobe doesn't document this clearly as webservices are so big now.
Thanks again

jones said...
This comment has been removed by a blog administrator.
Keelee said...

Thanks! This helped me a lot!! Woohoo

mlm said...

Just came across this blog when searching for web service invocation errors. Not sure if it will solve my specific problem, but it is very understandably written and has great information. Bookmarking it for later use!

Anonymous said...

Thanks a ton for the cfadmin tip!