Thursday, November 16, 2006

Mystery of the missing wsdl stub class: another clue(?)

As previously bemoaned here and here, I ran into a problem with ColdFusion failing to generate and compile all of the necessary java classes from Netsuite's wsdl when invoking a new web service object. Meanwhile, I run wsdl2java from the command line, and get each and every one of the 500+ java source code files from the wsdl.

Tell me, is Netsuite's wsdl really so complex that ColdFusion misses some of the class files? I find that very hard to believe. In any case, one of my coworkers today discovered that ColdFusion seems to come with two copies of Apache Axis, which includes the wsdl2java class. Apparently axis is not only in [cfusion install]/lib/axis.jar, but is also mixed in with other packages in [cfusion install]/runtime/lib/webservices.jar. So that makes me wonder if ColdFusion is using the version that I was not calling from the command line. I think it warrants a little test. Hopefully I'll get a chance tomorrow. I'll post my findings here.

UPDATE: I actually found a THIRD place where wdsl2java exists in the CF installation. I was able to test two of them, but both generated all of the java source files. Curious indeed.

Friday, November 10, 2006

Preventing ColdFusion from recompiling web service stub classes

UPDATE March 8, 2009: Since this was first posted, I verified that the incomplete stub file compilation issue appears to be an issue in ColdFusion 8, as well. I haven't tested it, but I have no reason to believe that the workaround described below wouldn't work in 8.

As I have been for the past few months, I'm currently working on an interface Netsuite via their web service. As posted here previously, it's brought up a few interesting challenges using ColdFusion. One annoyance was that following a ColdFusion service restart the next call to the Netsuite web service would take for-friggin-ever. I figured out it was because ColdFusion was refreshing and recompiling the stub Java classes generated from Netsuite's very deep and complex WSDL. Their WSDL results in over 500 distinct class files. From my local machine on which I was developing the code, it would take 5 to 10 minutes. When you're testing, screwing things up, and restarting ColdFusion regularly, this gets to be more than a little tedious.

I looked online to see if I could find any information about preventing ColdFusion from refreshing the stub classes, but no dice. I could only find information about how to force it to recompile. Not what I wanted. But perhaps someone out there knows of a server setting or some sort of parameter to prevent refreshing from the WSDL.

Now you might ask why I wouldn't want to make sure I have the latest and greatest from Netsuite. The problem is that for some unknown reason, ColdFusion refuses to generate and compile all of the necessary stub classes, so I can't rely on that process to get me everything I need. Perhaps the WSDL is too nested and complex? But that doesn't make sense because ColdFusion (I would presume) is using wsdl2java, which comes with Apache Axis. So why when I run wsdl2java from the command line does it successfully generate each and every java source file? I'm still scratching my head over that one. In any case, to ensure I had all necessary stub classes, I manually ran wsdl2java and compiled all of the resulting java files into class files from the command line, then whisked them away into a jar file that I dumped in the \CFusionMX7\lib directory.

Once I figured that the delay after the service restart was due to the stub classes being refreshed and ruled out preventing it with server settings or parameters, I realized I could simply avoid creating a ColdFusion web service object entirely by directly creating a java object from the stub classes. Unfortunately, the renders useless some of ColdFusion's handy functions like getSOAPRequest and getSOAPResponse.

Example:
<!--- Create a plain old coldfusion web service object (could also do a cfinvoke).---> 
<cfset ws = CreateObject("webservice", "https://webservices.netsuite.com/wsdl/v2_5_0/netsuite.wsdl")>
<!--- call some function from that web service --->
<cfset ws.MakeSomethingHappen()>
<!--- dump the entire soap request --->
<cfdump var="#getSOAPRequest(ws)#">
<!------ --- ----- ------ --------- ------------- --->
<!--- Create an equivalent java object directly from the service locator stub class (the class name "[web service name]ServiceLocator" and method "get[web service name]Port()" will be what to look for; i.e. NetsuiteServiceLocator.getNetsuitePort(), MyWebServiceNameServiceLocator.getMyWebServiceNamePort(), etc) --->
<cfset ws = CreateObject("java", "com.netsuite.webservices.platform_2_5.NetSuiteServiceLocator").getNetSuitePort()>
<!--- call the same function from that web service as the previous example --->
<cfset ws.MakeSomethingHappen()>
<!--- dump the entire soap request (it's a little messy, but you can always write your own udf that returns the same thing)... --->
<cfdump var="#ws._getService().getCall().getMessageContext().getRequestMessage().getSOAPEnvelope().toString()#">
<!--- ... or dump only the request body --->
<cfdump var="#ws._getService().getCall().getMessageContext().getRequestMessage().getSOAPBody().toString()#">