Showing posts with label Web Service. Show all posts
Showing posts with label Web Service. Show all posts

Thursday, April 23, 2009

Make ColdFusion 8 work with Apache CXF

"Alan" left a comment on one of my older posts ("ColdFusion 8 Getting Started code available") detailing how to get ColdFusion 8 to work with Apache CXF. I figured it would be useful to repost in its own entry.

DISCLAIMER: I have not tried this, nor do I make any guarantees that this wont do bad stuff to your server. Try it on a Developer Edition on your desk before messing with any production server.


If anyone is interested, I have been able to make ColdFusion 8 work with Apache CXF, which supports a variety of web service standards and libraries - SOAP 1.1, SOAP 1.2, REST, etc.

Caveats: CF8 works with CXF in the sense that CXF objects can be instantiated as Java objects in CFML and their operations successfully invoked. It does NOT work as a native CF web service call using or CreateObject("webservice", "..."). Also, this technique requires some low-level changes to the default CF8 installation, so it is not for the faint of heart.

In general, the procedure is to grab the CXF libraries and get CF to recognize them. You can drop them directly into a /lib folder that's in the CF classpath, or if you want to be a bit more cautious you can point the CF classpath to the folder that contains the CXF JAR files.

Detailed steps:
  1. Download the latest CXF distribution and extract/install it somewhere. If your CXF root is /foo, then find /foo/lib. You'll see a bunch of JAR files, and your mission is to get CF to recognize these files and load the classes in them.

  2. Stop the CF server.

  3. CF8 and the JRE that comes with it use an older, incompatible version of the JAXB library, so we need to get the newer one in place. Create /foo/lib/endorsed and copy the jaxb-api-version.jar file into that folder.

  4. Find jvm.config in your CF installation and open it in a text editor. Append the following to the JVM arguments: -Djava.endorsed.dirs=path_to_foo/lib/endorsed -Djava.protocol.handler.pkgs=javax.net.ssl. Find the string -Dcoldfusion.classPath= in the JVM arguments and place the path to /foo/lib immediately after the equals sign (i.e., at the FRONT of the classpath), with a comma to separate it from the rest of the classpath entries. Do not set the classpath using the CF administrator. It will not permit adding elements to the front of the classpath, and will in fact overwrite the classpath if it is used to change Java settings at any future point.

  5. A suspected bug in the CF classloader causes the wrong part of the CF architecture to load the SAAJ classes. [Tom Here: This isn't a bug, CF maintains absolute control over which classes it loads via its own classloader. A better workaround would be to edit the jrun.properties file in the WEB-INF/lib/cfmx_bootstrap.jar file and change the exceptions list] Get around this by copying /foo/lib/saaj-api-version.jar into {java.home}/lib of your CF installation. Then delete or rename saaj-api-version.jar in /foo/lib so it does not get loaded from that location. Also delete or rename saaj.jar from the main CF library location (ColdFusion8\lib on a Windows installation, standalone configuration).

  6. Disable the native JAXB library in ColdFusion by deleting or renaming jaxb-impl.jar in the main CF library location.

  7. Restart the CF server.


With these steps, you will be able to invoke Java objects that serve as clients for the web service endpoints. These Java objects need to be created, compiled, and installed to a location in the CF classpath in order to be invoked. The CXF documentation describes ways to create the Java objects; wsdl2java might be your best bet. You can have it create classes that are SOAP 1.1 and SOAP 1.2 compatible, allowing you to call those services via the Java objects from inside ColdFusion files.

After doing this, I did a cursory test of the native CF web service functionality (Axis 1.1) and it seems to work still, so existing code shouldn't be affected.



There ya go, I would be interested in posting an update if Alan or someone else figures out what the classloading issues with the SAAJ libraries are.
Thanks Alan!

Wednesday, July 02, 2008

Special Axis types and ColdFusion

A mere hours after I posted about how to figure out why CF does not like the arguments you are passing to invoke an operation in a web service, Sean Corfield pinged me with a problem exactly like that. Feeling pretty good, I pointed him to the post I made hours before. He of course had already tried the wsdl2java trick and still could not get CF to do the deed.

He sent me the WSDL and his test and everything looked OK. It was a pretty complex input to an operation (names changed to protect privacy):
public com.example.RegisterResponse register(com.example.RegisterRequest parameters)

The RegisterRequest was a JavaBean that included the following members:

private java.lang.String sessionID;
private com.example.ThingyType importantThing;
private com.example.ThingyType otherThingy;
private com.example.ThingsILike likeList;
private org.apache.axis.types.UnsignedInt duration;

Fun stuff. Sean had done all the right things creating CFML structs that matched each of the JavaBean types (ThingyType, and ThingsILike). ThingsILike was interesting because it contained a single item that was an array. His code was right on the money here, notice that the ThingsILike object had a single member named "things" that was the array. Here is what he did and (rightfully) expected to work:

thing1 = { name = "bobby", location = "Portland" };
thing2 = { name = "sally", location = "Boston" };
iLike = { things = ["Bunnies", "Kittens", "Koalas", "ColdFusion"] };
args = { sessionID = 0, importantThing = thing1, otherThingy = thing2, likeList = iLike, duration = 60 };

Bonus points to Sean for using the new ColdFusion 8 syntax to create arrays and structures. So the array wasn't the problem. What was? Well you may have noticed that duration is listed in the Java function as being of type org.apache.axis.types.UnsignedInt. this is because the XMl Schema type in the WSDL says that the number is an Unsigned Integer:

<xsd:element name="Duration" type="xsd:unsignedInt" />

As an aside here, XML Schema has a lot of different types that elements can be. Things like NonNegativeInteger, nonPositiveInteger and other slightly wacky things. Java on the other hand doesn't have any of these. So the Axis folks (which included me) came up with a set of classes that would enforce the limitations of the Schema types and allow you to 'round trip' a service that you generated from a WSDL, then deployed and allowed Axis to create the WSDL from the Java. This is good, and when you are writing Java directly against the WSDL2Java generated code no big deal because you can pretty quickly notice that you need one of these types and make one.

But back to ColdFusion. In order for the "60" given in the code to turn in to the class UnsignedInt, ColdFusion has to be smart about how to construct this object. It's not. However there is a simple work around - create the object yourself. Here is how that would look:

duration = CreateObject("java", "org.apache.axis.types.UnsignedInt").init(60);

Notice that I call init(60), which invokes the constructor of the class. Setting this object as the value of duration in our argument structure works like a charm. So problem solved, and I hope that we can make CF smarter about these types in a future release, but encountering these XML Schema types in the wild is pretty rare so I don't expect you will need this workaround much if at all.

Using WSDL2Java to figure out CFML arguments to a Web Service

For some reason I haven't actually even written up the procedure I use to figure out how to invoke a web service from ColdFusion when people ask me what CFML structures they need to pass in so CF can match the arguments to a function in the Apache Axis stub it generated from the WSDL.

I encourage you to read the reprint of Consuming Web Service complex types in ColdFusion which goes in to some great detail on how to figure this out. This posting is going to be the cheat sheet version of that.

First, if you can't figure out why CF gives you an error about not finding the right function to invoke, you probably aren't passing in the right arguments. How do you figure these out? You run the WSDL2Java command in Apache Axis, and take a look at the Java code CF is trying to invoke. Here's a batch script for Windows and ColdFusion 8:

set CFLIB=c:\ColdFusion8\lib
set CLASSPATH=%CFLIB%\axis.jar;%CFLIB%\wsdl4j-1.5.1.jar;%CFLIB%\log4j-1.2.12.jar;%CFLIB%\commons-logging.1.0.4.jar;%CFLIB%\commons-discovery-0.2.jar;%CFLIB%\xercesImpl.jar;%CFLIB%\xml-apis.jar;%CFLIB%\saaj.jar;%CFLIB%\jaxrpc.jar;%CFLIB\..\runtime\lib\jrun.jar

java org.apache.axis.wsdl.WSDL2Java %1 %2 %3 %4 %5 %6 %7 %8 %9


Save this as "wsdl2java.bat". This will also work with ColdFusion MX 7. You will need to change the log4j-1.2.12.jar file to be just log4j.jar.

Change to an empty directory. Wsdl2Java is going to spew directories and files all over the current directory, so I find its best to create something like c:\temp\x and run the command from there. Otherwise you can give the -o option and specify the output directory.

wsdl2java.bat -v -o c:\temp\x http://example.com/path/to/wsdl

The -v is the verbose argument. I like to see all the files that are being created.

Now you can start browsing through the Java files and see what we have to work with. Look for a file named the same as the "portType" name in your WSDL. For instance, if your WSDL contains a portType that looks like this:
<wsdl:portType name="CodexWS">

Then you would look for CodexWS.java. This will be the interface of the service, and you should be able to see each of the operations in your service, and the Java objects that ColdFusion will be trying to create to call them.

How do you create the CFML to pass to the operation? Simple. Most likely there will be JavaBeans created from the XML Schema in the WSDL. These aren't that big of a deal, basically they are structures with name/value pairs, which is why you will need to make a CFML structure with the same names in it.

Check out this Bean:

public class AddThingRequest implements java.io.Serializable {
private java.lang.String manifestfile;
private java.lang.String uri;
private java.lang.String certlevel;
private java.lang.String statusname;
private com.adobe.Ldapcredentials ldapcredentials;
...

There would be a getManifest() and setManifest() function in the bean also, along with a get and set function for each one of the other bean properties. These only matter because this is what CF will look for when it finds a "manifest" entry in your CFML structure. So what would we pass in to an operation that takes an "AddThingRequest" object as an argument? Something like this:

atr = StructNew();
atr.manifestfile = "foo";
atr.uri = "uri:gimme";
atr.certlevel = "high";
atr.statusname = "Alert";
art.ldapcredentials = mycreds;

But wait, you say, what is "mycreds"? Well, the process just repeats here - we would go find the com.adobe.Ldapcredentials Java object, look at the properties it contains and make a CFML structure (mycreds in this example above) that matches it.

So that's all (ha!) there is to it. You now have the magic to invoke almost any web service out there. This isn't exactly "making the hard stuff easy" (that would be that WSDL2CFML tool I haven't written yet), but once you understand how the process works, you should very rarely have to resort to grunging through the Java code generated from a WSDL to construct the right CFML inputs. My opinion is that Web Services should be designed to present an easy to use interface, simple inputs with only reasonably complex outputs. I know that isn't the case for many services out there that you are forced to used. But with this technique you should be able to easily figure out how to use the more complex ones.

Thursday, April 10, 2008

Reprint: Consuming Web Service complex types in ColdFusion

In previous posts I have referenced an excellent article written by Doug James and Larry Afrin from the University of South Carolina. The link for that article has since gone dead, but someone helpfully posted a link to the article via a web archive. I am going to reproduce it here so I have an easy reference for it and so its close to my earlier post on array types.


Notes on Interfacing ColdFusion MX to External Web Services Requiring Complex-within-Complex XML Documents as Input


Authors:
Doug James (jamesd@musc.edu)
Larry Afrin, MD (afrinl@musc.edu)
Hollings Cancer Center

Medical University of South Carolina
March 31, 2005

<cfacknowledgement>
The authors would like to acknowledge Macromedia's Tom Jordahl (who we understand is sort
of the principal developer and "guru" of ColdFusion's web services functionality) both for his
assistance in helping them understand how ColdFusion handles certain complex web service
interactions and for his critical review of the following document.
</cfacknowledgement>

<cfdisclaimer>
While the authors have tried to ensure the accuracy and utility of the information below, they
offer the following information with no warranties whatsoever and hereby explicitly state that their employer has had absolutely nothing to do with the development of this document and
therefore also bears no liabilities with regard to how the information presented here may be used.

The authors are also quite sure that despite Tom Jordahl's review of this document, Macromedia
takes no responsibility for this information, either. By their posting of this information, the
authors are not offering themselves as support resources for other developers wrestling with the
problems addressed herein. Requests for assistance in this area that are communicated to the
authors may or may not be acknowledged or answered, solely at the authors' whim. After all, we
do have to attend first to our day jobs (which often spill over into our night jobs, too.) ;-)
</cfdisclaimer>

=======================================================

CFMX is able to communicate with web services hosted in arbitrary computing environments as long as the SOAP standards are followed. The SOAP standards require that all input arguments to a web service be passed as XML documents. The expected format of the XML document for any given input argument is defined either directly in the service's WSDL (Web Services Description Language) document, or by reference in that WSDL document to another data element definition document.

Before proceeding further to discuss how CFMX handles "complex"-type web service input arguments, it is helpful to review how CFMX handles a <cfinvoke> (or equivalent) request in general. CFMX first retrieves the target service's WSDL, then runs this WSDL through the WSDL2Java tool (see below for more information), which outputs Java code defining Java-based classes equivalent to the XML data structures defined in the WSDL. This Java code is then compiled. The input arguments provided to <cfinvoke> are then mapped to the Java types generated by the compilation, and finally the Java stub functions for those types are called. In this fashion, CFMX *automatically* converts input values referenced in <cfinvoke> or
<cfinvokeargument> tags to XML documents of the appropriate formats based on input argument format information provided in the service's WSDL. The burden on the coder, of
course, is to ensure the input values are structured in accordance with what CFMX expects to
find based on the translation by WSDL2Java from an XML-based data structure to a Java-based
data structure.

A similar process is followed to handle the service's return value and other output arguments.

This process is quite straightforward for "simple" type input arguments, such as simple strings or numeric values. Because ColdFusion variables, strictly speaking, are untyped, CFMX
automatically converts simple input ColdFusion variables to the equivalent string-based simple
XML structures.

However, some web service input arguments are XML documents of "complex" type. As briefly
described in the ColdFusion MX 7 documentation at: http://livedocs.macromedia.com/coldfusion/7/htmldocs/00001554.htm for complex-type arguments, CFMX expects to find a ColdFusion structure provided as the value for the input argument. Unfortunately, the ColdFusion MX documentation only provides clear examples of how to compose a ColdFusion structure that corresponds merely to a relatively simple form of a "complex" XML document in which the child elements of an outer "complex" parent element are simple scalar values, as illustrated in Example 1:

---------Example 1:------------------------------------
WSDL snippet:

<s:complexType name="Employee">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="fname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="lname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="age" type="s:int" />
</s:sequence>
</s:complexType>

Sample XML document:

<Employee>
<fname>John</fname>
<lname>Smith</fname>

<age>25</age>
</Employee>
CFML snippet:
<!--- Create a structure using CFScript, then call the web service. --->
<cfscript>
stUser = structNew();
stUser.fname = "John";
stUser.lname = "Smith";
stUser.age = 23;
ws = createObject("webservice", "http://somehost/echosimple.asmx?wsdl");
ws.echoStruct(stUser);

</cfscript>
-------------------------------------------------------

While the above is helpful for "simple" complex-type input arguments, the ColdFusion MX
documentation provides no examples of how to compose a ColdFusion structure that corresponds to a more complex form of a complex-type XML document in which the child elements of an outer "complex" parent element are themselves "complex" parent elements (so-called "complex-within-complex" XML documents), as illustrated in Example 2:

---------Example 2:------------------------------------

WSDL snippet:

<s:complexType name="Employee">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="fname" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="lname" type="s:string" />
<s:element minOccurs="0" maxOccurs="unbounded" name="nickname" type="s:nickname" />
<s:element minOccurs="1" maxOccurs="1" name="age" type="s:int" />
<s:element ref="s:address" minOccurs="0" maxOccurs="unbounded" name="address" />
</s:sequence>
<xsd:attribute name="employeeGender" type="string" use="required"/>
</s:complexType>

<s:complexType name="nickname">
<s:simpleContent>
<s:extension base="string" />
</s:simpleContent>
</s:complexType>

<s:complexType name="address">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="street" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="street" type="s:string" />
<s:element minOccurs="1" maxOccurs="1" name="state" type="s:state" />
<s:element minOccurs="1" maxOccurs="1" name="zip" type="s:zip" />
</s:sequence>
<xsd:attribute name="addressType" type="string" use="required"/>
</s:complexType>
Sample XML document:
<Employee employeeGender="Male">
<fname>John</fname>
<lname>Smith</fname>
<nickname>Jack</nickname>
<nickname>Johnny</nickname>
<age>25</age>
<address addressType="Home">
<street>25 Main Street</street>
<city>Townville</street>
<state>Anystate</state>
<zip>99999</zip>
</address>
</Employee>
CFML snippet:
<!--- Create a structure using CFScript, then call the web service. --->
<cfscript>
stUser = structNew();
stUser.fname = "John";
stUser.lname = "Smith";
stUser.age = 23;
<!--- If .x corresponds to *element* <x>, then what syntax is used to specify an *attribute*?
How should employeeGender get set up in this struct? Read on to find out. --->
<!--- And what about the *two* nicknames? stUser.nickname = ... clearly won't work.
Read on to find out how this is handled. --->
<!--- And what about <address>? Does that get coded simply as stUser.address = structNew(),
stUser.address.street = "25 Main Street", etc. etc.????? The answer is "No."
Read on to find out more. --->
ws = createObject("webservice", "http://somehost/echosimple.asmx?wsdl");
ws.echoStruct(stUser);
</cfscript>
-------------------------------------------------------
There are just a few key (pardon the pun) principles you need to understand in order to determine how to compose a CF structure that CFMX will map to the properly formatted XML document needed as an input argument to an arbitrary web service:

(1) Principle #1: Any given key in a CF structure will be mapped by CFMX into *either* an
element name *or* an attribute name depending on what role the WSDL says that particular
name should play at that level in the document. In other words, you do not need to differentiate
between attributes and elements in the CFML structure you create; ColdFusion will automatically
handle for you the proper mapping of each key into either an attribute or element as required by
the WSDL.

Following along with Example 2 above, then, stUser.age will get translated into an <age> child *element* within the <Employee> parent element, and stUser.employeeGender will get translated as the employeeGender *attribute* within the <Employee> element.

(2) Elements in a WSDL that can occur more than once (cf. the "address" element in Example 2 above) are represented in the CF struct as an array. OK, but an array of what? Well, it depends on how the WSDL defines the subelements.

Again, following along with Example 2 above, <address> would be coded into the stUser
structure as follows:

  stUser.address = arrayNew(1);
stUser.address[1] = structNew();
stUser.address[1].street = "25 Main Street";
stUser.address[1].city = "Townville";
stUser.address[1].state = "Anystate";
stUser.address[1].zip = "99999";
stUser.address[1].addressType = "Home";
and the nicknames would be coded as follows:
  stUser.nickname = arrayNew(1);
stUser.nickname[1].value = "Jack";
stUser.nickname[2].value = "Johnny";

Hey! Where did ".value" come from? Read on:

(3) As the complexity of the format of the required input XML document increases, it may
become increasingly difficult to "guess," using the above two principles, how the corresponding CF structure should be coded. (Similarly, when working with web service return variables, or
output arguments, of complex-within-complex type, it may become difficult understanding why
CFMX has translated the output into the rather complex structure revealed by <cfdump>.) To help work through this, the coder should apply the WSDL2Java tool (see below for more
information) against the target service's WSDL and carefully examine the output of this tool.
WSDL2Java is an open source utility that takes a WSDL as input and outputs the Java beans
corresponding to the methods of the web service described by the provided WSDL; importantly,
the inputs and outputs for these methods are also defined in the corresponding beans. Upon
careful examination, the WSDL2Java output -- i.e., the Java code defining each bean -- clearly
identifies the expected layouts of the CF structures corresponding to the inputs and outputs of the service's various methods.

For example, one of the things that becomes clear upon examining the WSDL2Java output has to
do with the use of ".value" above. This is complicated, so read carefully: If an element (or
sub-element) of an input argument defined in the WSDL is declared in the WSDL to be of
"complex" type, but the corresponding definition in the WSDL of that complex datatype declares
that the value of that datatype is in fact only a simple scalar value, then CFMX assumes the value to be passed as input for that element (i.e., the value placed between the <x> and </x> tags) will be found in the corresponding CF structure (at the appropriate level) in association with a key named "value".

Below is another example of a web service input argument formatting problem that was solved by examining the output of the WSDL2Java tool. This is a real-world example of using CFMX to interface to a UDDI server. UDDI stands for Universal Data Discovery & Integration. UDDI servers are sort of the Domain Name System (DNS) of the web services world. UDDI servers store information about web services, including their addresses. A given web service could have many implementations around the world. An application that wants to make use of these implementations doesn't have to know their addresses as long as (1) the service and its implementations are registered in UDDI, and (2) the application knows the address of at least one UDDI server (part of the UDDI standard includes, a la the Network News Transport Protocol (NNTP, used by Usenet discussion groups), a replication protocol so that UDDI servers exchange database updates with each other in order for them all to stay in sync). UDDI servers can have a number of programmatic interfaces (e.g., URL-parameterized HTTP GETs), but the most robust interface they support is a web service interface. That's right: you can use web services to query UDDI servers about web services. However, the WSDL for the web service one uses to query a UDDI server is quite complex. More information about UDDI can be found at http://uddi.org.

Here's an example of using the "find_business" method defined in the UDDI WSDL to retrieve information from the target UDDI server about the first 50 businesses known to that server whose names begin with "A":

---------Example 3:------------------------------------

WSDL snippet:
<xsd:complexType name="find_business">
<xsd:sequence>
<xsd:element ref="uddi:name" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="uddi:identifierBag" minOccurs="0"/>
<xsd:element ref="uddi:categoryBag" minOccurs="0"/>
<xsd:element ref="uddi:tModelBag" minOccurs="0"/>
<xsd:element ref="uddi:discoveryURLs" minOccurs="0"/>
<xsd:element ref="uddi:findQualifiers" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="generic" type="string" use="required"/>
<xsd:attribute name="maxRows" type="int" use="optional"/>
</xsd:complexType>

<xsd:complexType name="name">
<xsd:simpleContent>
<xsd:extension base="string">
<xsd:attribute ref="xml:lang" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
CFML snippet:
<cfscript>
// define the main outer structure, findBiz in this case, can be named anything one chooses
findBiz = structNew();

//generic and maxRows are attributes (required and optional, respectively) of the uddi:find_business tag
findBiz.generic = "2.0";
findBiz.maxRows = 50;

//uddi:find_business tag accepts one *or more* "name" values, so an array is used
findBiz.name = arrayNew(1);
findBiz.name[1] = structNew();

//uddi:name tag is a complex type with a string value, so 'value' is used as the key to the structure
findBiz.name[1].value = "A";
</cfscript>

<!---
Important note:
In the <cfinvoke> below, the URL to the UDDI WSDL is the value of the "webservice"
parameter; the "method" parameter specifies the UDDI inquiry method called "find_business",
which returns a uddi:businessList object in the returnVariable "busList".

In the UDDI WSDL, the "find_business" method is defined as requiring an input parameter
named "body" of complex type "find_business" which is also defined in the WSDL. The
parameter name "body" shown in the <cfinvoke> below could also have been set up as a
<cfinvokeargument name="body" value="#findBiz#"> tag within the <cfinvoke>.

The actual version 2.0 UDDI WSDL is at http://uddi.org/wsdl/inquire_v2.wsdl. This address
cannot be referenced as the "webservice" parameter in the <cfinvoke> tag because this particular
WSDL does not define the addresses of any actual UDDI servers. Thus, to query a UDDI server,
one has to copy this "generic" WSDL to somewhere else and modify it by adding in the proper
WSDL code to identify at least one actual UDDI server. The <cfinvoke> is then pointed at this
modified WSDL. CFMX will read the modified WSDL from this location, and in this modified
WSDL CFMX will find where it has to go to contact the actual UDDI service being targeted by
the coder.

The WSDL code that identifies an actual UDDI server is as follows. This happens to be the
real code for pointing at the main IBM UDDI server. This code gets inserted just ahead of the
closing </definitions> tag of the "generic" UDDI WSDL.

<service name="InquireSoap">
<port name="InquireSoap" binding="tns:InquireSoap">
<soap:address location="http://uddi.ibm.com/ubr/inquiryapi" />
</port>
</service>
--->

<cfinvoke
webservice = "http://www.webhost.com/modified_uddi_v2.wsdl"
method = "find_business"
body = #findBiz#
returnvariable="busList">
</cfinvoke>
-------------------------------------------------------


And another example from the world of UDDI, this time using the get_businessDetail method:

---------Example 4:------------------------------------

WSDL snippet:
<xsd:complexType name="get_businessDetail">
<xsd:sequence>
<xsd:element ref="uddi:businessKey" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="generic" type="string" use="required"/>
</xsd:complexType>

<xsd:simpleType name="businessKey">
<xsd:restriction base="string"/>
</xsd:simpleType>
CFML snippet:
<cfscript>
// busDetail is outer main structure
busDetail = structNew();

// generic is the required attribute of the "get_businessDetail" tag
busDetail.generic = "2.0";

//businessKey tag can occur many times, so it is mapped to an array
busDetail.businessKey = arrayNew(1);

/*
Because the "get_businessDetail" tag has attributes and sub-elements, the sub-elements have to
be mapped into structures as well, and, similar to the uddi:name above, the sub-element
"businessKey" just contains a string, so the key name 'value' is used. */

for (x = 1; x LTE arrayLen(busKeys); x = x + 1) {
busDetail.businessKey[x] = structNew();
busDetail.businessKey[x].value = busKeys[x]; //busKeys is an array predefined from another process
}
</cfscript>

<!---
As in Example 3, the UDDI WSDL declares that an input parameter named "body", of
complex type "get_businessDetail", is required for the "get_businessDetail" method.
--->
<cfinvoke
webservice = "http://www.webhost.com/modified_uddi_v2.wsdl"
method = "get_businessDetail"
body = #busDetail#
returnvariable="busEntity">
</cfinvoke>
-------------------------------------------------------

MORE INFORMATION ABOUT WSDL2Java:

WSDL2Java is a Java program that was created by the Apache group and is included in the CFMX distribution with the Axis package, the Apache group's implementation of the W3C SOAP standard.

To run WSDL2Java from the command line:

  1. The current directory needs to be the ColdFusion installation's 'lib' directory:
    • Windows: C:\CFusionMX\lib
    • RedHat Linux: /opt/coldfusionmx/lib

  2. Set classpath to include the following jar files:
    • RedHat Linux:
      • axis.jar
      • saaj.jar
      • jaxrpc.jar
      • xercesImpl.jar
      • wsdl4j.jar
      • commons-logging-1.0.2.jar
      • commons-discovery.jar
      • xml-apis.jar
      • activation.jar (This jar file can be found by adding runtime/lib/jrun.jar to the end of the classpath. Alternatively, it can be downloaded from Sun's Java Activation Framework web site.)

    • Windows:
      • SET
        CLASSPATH=axis.jar;saaj.jar;jaxrpc.jar;xercesImpl.jar;wsdl4j.jar;commons-logging-1.0.2.jar;commons-discovery.jar;activation.jar;xml-apis.jar

  3. Run the WSDL2Java program:
       Usage:  java org.apache.axis.wsdl.WSDL2Java [options] WSDL-URI
    '-v' option prints informational messages
    '-o' option is the output directory for emitted files.
    WSDL-URI can be either local or remote


    Example: java org.apache.axis.wsdl.WSDL2Java -v -o C:\wsdl2java_output http://uddi.org/wsdl/inquire_v2.wsdl

  4. More information about WSDL2Java can be found on the Apache Web Service web site, currently http://ws.apache.org/axis/java/user-guide.html#WSDL2JavaBuildingStubsSkeletonsAndDataTypesFromWSDL.


Tuesday, April 08, 2008

Array types in ColdFusion web services

I get asked questions about publishing web services in ColdFusion often and this is one that many folks run in to that I wanted to post about as I just got asked this. Usually people send mail to Ben Forta, and he just forwards them on to me. :-)

Question - How do I create a function that has an array of custom made objects as an argument or return value?

First, let me recommend reading the ColdFusion 8 Developers guide chapter on web services (http://livedocs.adobe.com/coldfusion/8/htmldocs/webservices_01.html). I would also recommend reading Ben Forta's (et al) book, the Web Application Construction Kit Volume 3, chapter 68 – Creating and Consuming Web Services.

The first piece of information to know is that the CFML complex types, such as they are, might not the best things to use when creating a Web Service. Lets take Struct's for example. When you define an argument to a function as a struct, the XML Schema that is emitted for the WSDL defines an object that has the "any" type as both the key and the value. But this doesn't give the consumer of the web service much information - are there strings or complex types as the key? What kind of values should there be? Should there be various different things contained in the structure?

A better way to create this service is by defining exactly what kind of things you expect or return. To do this you would create a ColdFusion Component (CFC) that used the (mostly useless except for web services) cfproperty tag to describe the structure. Lets say our structure contained a just string pairs. Here is how you would define that:

<cfcomponent>
<cfproperty name="key" type="string">
<cfproperty name="value" type="string">
</cfcomponent>

Now our XML Schema in the WSDL would define a complexType that has two element in it at key (of type string) and value (also of type string). This has the advantage of clarity and also is potnetially much more interoperabile.

But the original question was about arrays. Lets get back to that.

If you want to publish a web service using CFC's then you would define the cffunction that takes an array as an argument or as a returnType. You can define this array to be of a particular CFC type that you have defined using the cfproperty tag as we did above. The example in the Forta book (Volume 3, page 298, listing 68.11 and 68.12) also shows this. The missing piece is how to specify that the argument is an array.

<cffunction name="GetCreditRating" returntype="string" output="no" access="remote">
<cfargument name="person" type="CreditPerson[]" required="yes">

Notice the "[]" after the name of the component. This will indicate that the WSDL should define the CreditPerson complexType and that the argument to the function should be 1 or more (MaxOccurs="unbounded") of these complex type elements i.e. an array. You can use this with types defined by CFCs or even for simple types (e.g. string[]). This syntax is valid in both the returnType attribute and the type attribute of cfargument.

This information is in the ColdFusion 8 documentation at
http://livedocs.adobe.com/coldfusion/8/htmldocs/webservices_20.html

The follow up to this is if you want to consume a web service (such as the one above) you would define an array in CFML and put in it Structs that correspond to the complex type in the WSDL. Here is an example again based on the GetCreditRating that uses the new CF8 syntax for creating stuctures:

<cfscript>
arg = ArrayNew(1);
s1 = { FirstName=Tom, Lastname=Jordahl, …};
s2 = { FirstName=Ben, Lastname=Forta, …};

arg[1] = s1;
arg[2] = s2;
</cfscript>

Then you would pass the “arg” array as the parameter to the invocation of the web service.

I hope this fleshes out a little but about array types for ColdFusion web services.

Thursday, February 28, 2008

Using ColdFusion with SQL Server XML/SOAP Endpoints

John Jarrad has blogged a very nice explanation on how he got ColdFusion 8 working with SQL Server 2005's XML/SOAP Endpoints. John referenced a few of my older posts like the one about how to use the Apache Commons HTTPClient instead of the built in HTTPSender class.

The interesting thing is that he did not use the HTTPClient library (although using this should have allowed the Digest and NTLM authentication to work since the library supports it) because the HTTPSender class (see source here) does in fact support HTTP 1.1, just not by default. The trick is to set the right property on the web service stub so that the Axis engine does the right thing.

The other note to John's post is that you can use cfinvoke if you want to, but you just have to use cfobject/CreateObject to create the web service "stub" first, then pass that in as the webservice attribute of cfinvoke.


<cfset ws = CreateObject("webservice", "https://webservice.yourcompany.com/sql/whatever_endpoint?wsdl")>
<cfset ws._setProperty("axis.transport.version", "1.1")>
<cfinvoke webservice="ws" method="JJData" returnvariable="result">
</cfinvoke>


John also notes that processing the result set and turning it in to a query is pretty slow. Not sure what is causing this other than there is a large amount of data getting processed.

Tuesday, September 11, 2007

ColdFusion 8.0 Cumulative Hot Fix 1

ColdFusion 8.0 Cumulative Hot Fix 1 has been posted to the ColdFusion support site.

Fixes include:

  • Debugger steeping issue when Break On Exception is turned on
  • A fix to ensure that user included CSS style sheets come after the CF files so you can override the default styles in Ajax controls
  • A bug with cfzipparam that broke it when using it in a loop
  • A tweak for the cfpdf tag so it preserves the "Enable Usage Rights" flag in the PDF. This is something you can turn on in Acrobat.
  • A bug in CreateObject for components that manifested itself under heavy load
  • A fix for certain cfdocument table rendering
  • A fix for datasources defined in JRun not working in ColdFusion

Also, for those who haven't upgraded to ColdFusion 8 (why not?) we have posted ColdFusion 7.0.2 Cumulative Hot Fix 3 which fixes another dozen issues with CFMX 7.0.2.

Both of these have some good stuff in them and I recommend that (after proper testing with your applications) they get deployed on all of your servers.

Wednesday, June 21, 2006

How to get web service response cookies

So yesterday I wrote about how to get the Axis engine that underlies the ColdFusion web services to send cookies in the request. The follow on question asked shortly after I posted that was how do I get cookies sent to me from (say) an initial login request to the web service.

Here is how to do that:

ws = CreateObject("webservice", ...);
ws.setMaintainSession(true); // required so axis will do cookies
ret = ws.log_in_or_something();
...
// Get cookies returned from server (if any)
call = ws._getCall();
ctx = call.getMessageContext();
server_cookies = ctx.getProperty("Cookie");
Getting the cookies out of the Axis MessageContext will return either a single string if there is one cookie, or an array of Strings if there are more than one. One thing to watch out for is if there are not any cookies returned from the server, the return value will be (Java) null, so you will need to used isDefined() on that variable before using it. The other thing to watch out for is that the MessageContext property 'Cookie' will keep the values that you have set in to it if you set cookies before makeing a call. They will only get overwritten if the server send something back.

Tuesday, June 20, 2006

How to set Cookies in ColdFusion SOAP requests

I spent some time today working with a customer who is using a web service that requires its users to set HTTP cookies in their SOAP requests. First off, I think this is just wrong as this is what SOAP headers are used for and ColdFusion MX 7 (6.1 too) can easily set SOAP headers in both requests and responses.

In any case, Apache Axis can include HTTP cookies in the requests it sends out and since ColdFusion uses Axis as its implementation engine, you can do it in CFML also.

First, how would you set a cookie (or cookies) in Java? Here is some sample Java code:

    mystub._setProperty(
HTTPConstants.HEADER_COOKIE,
new String[]{"myCookie1=hello", "myCookie2=goodbye"});

This calls the "_setProperty" function on the Stub object, passing it a string ("Cookie") and an array of strings, which are the cookies to set. It turns out that the Axis code will look at the second argument and if it is a single String, it will set that as a cookie. If it is a String array, then it will treat each string in the array as a different cookie header.

So, how do we duplicate this in CFML?

First, realize that when you create a "webservice" object in CFML, this can be treated as the org.apache.axis.client.Stub class using the CF/Java interop. So in the case of setting a single cookie, we have no problems:


ws = CreateObject("webservice", "path-to-wsdl");
ws._setProperty("Cookie", "myCookie1=hello");

But this doesn't work! Why not? Well for those of you following along in the Apache Axis source tree, you will notice that the org.apache.axis.transport.http.HTTPSender.java class has this code in it (around line 260):

// don't forget the cookies!
// mmm... cookies
if (msgContext.getMaintainSession()) {
fillHeaders(msgContext, HTTPConstants.HEADER_COOKIE, otherHeaders);
fillHeaders(msgContext, HTTPConstants.HEADER_COOKIE2, otherHeaders);
}

So we need one more thing, we have to turn on the "maintain session" switch in the message context. This turns out to be simple to do as there is an API on the Stub to do just that:

ws.setMaintainSession(true); // required so axis will do cookies

Great, we add that and we can set one cookie for any web service operation we invoke. But what if we want to set more than one (this to-be-nameless service requires three!)?

Well, we need to create an array of Java Strings to pass in to _setProperty(). What happens if we try and use a CFML array? Well, usually CF is really good about taking its (mostly) typeless things and converting them to particular java types. But in this case the API signature for _setProperty is this:

public void _setProperty(String name, Object value);

So CF happily passes the CFML Array in to this API as an object. But deep down in the Axis code (HTTPSender.java, line 534 or so), it tries to treat this object either as a string array (String[]) or a String. Neither of which works in this case. You next thought might be to use the JavaCast() CFML function, as this is one of the cases that it is designed for. But unfortunately it only handles scalar (simple) types (yes, we need to enhance that). What next?

Well, in CFML you can create an Java object that you want using CreateObject. It just so happens that the java.lang.reflect.Array class has functions that will create arrays. So we can use the newInstance() function to allocate an array. This function takes in a Class object and the number of elements in the array.

string = CreateObject("java", "java.lang.String");
array = CreateObject("java", "java.lang.reflect.Array");
cookies = array.newInstance(string.getClass(), 3);

This creates a 3 element String array. One bug that this uncovers is that you can not treat this array the same as a CFML array. For instance you can not assign cookies[1] = "x=1" because ColdFusion will report a problem with casting "coldfusion.runtime.Cast$1", which is a really bad error message that makes sense if you could read the source code like I did, but doesn't help at all. Suffice to say that we are doing the wrong thing with a Java array when trying to set a value in that array. So what can we do? The Java Array class comes to the rescue again as we see that it has a set() API that takes an array, the index and the value you want to set. So we can call it like this:

array.set(cookies, 0, "x=1");
array.set(cookies, 1, "x=2");
array.set(cookies, 2, "x=3");

Watch out for the zero (0) indexed Java array, which differs from the one (1) indexed arrays in CFML.

We are then all set, we can use our cookies array as an argument to _setProperty and since we know it is a String[] (and CF wont change that) it will get down to the Axis HTTP class and be sent out in the HTTP request.

Here is the complete cfscript code:

// Create the array for cookies
string = CreateObject("java", "java.lang.String");
array = CreateObject("java", "java.lang.reflect.Array");
cookies = array.newInstance(string.getClass(), 3);
// set the cookie values
array.set(cookies, 0, "cookie1=one");
array.set(cookies, 1, "cookie2=two");
array.set(cookies, 2, "cookie3=three");

ws = CreateObject("webservice", "http://localhost:8500/test.cfc?WSDL");
ws.setMaintainSession(true); // required so axis will do cookies
ws._setProperty("Cookie", cookies);

// invoke an operation
ret = ws.echo("hi");

Friday, December 02, 2005

Changing the target endpoint on a web service object

Just answered a question for a customer that I should probably get out into the googlesphere for others to know.

When you create a web service object in ColdFusion MX, what you are really creating is a Java object that acts as a proxy to the web service. This object derives from an Apache Axis type, org.apache.axis.client.Stub, which in turn implements the standard JAX-RPC type javax.xml.rpc.Stub. Why is this important? Well, these object have an API that you can use to affect the operation of the proxy.

Specifically, you can change the endpoint that the stub will send the HTTP POST that contains the SOAP request. Here is a code snippet that invokes a CFC web service named service.cfc that has an operation "echo" which takes a string and returns it.

ws = CreateObject("webservice", "http://localhost:8500/service.cfc?WSDL");
ret = ws.echo("hello world");


Now lets say we want to watch the request with tcpmon, the TCP/HTTP sniffer that is bundled in with Axis, JRun and ColdFusion. First we start up tcpmon (called 'sniffer' by JRun), on Windows you run c:/CFusionMX7/runtime/bin/sniffer.exe.

Fill in a listener port (try 8501) and then a target hostname (localhost) and port (8500 if CF is running its internal webserver, or port 80 if you are running a standard web server). Click the "Add" button and then switch to the "Port 8501" tab. Just to make the output look nice, check the "XML Format" check box at the bottom so tcpmon will make the SOAP more readable.

Now we can run this CFML cfscript snippet:


ws._setProperty("javax.xml.rpc.service.endpoint.address", "http://localhost:8501/service.cfc");
ret = ws.echo("Through the tunnel");


You will see the SOAP request in the to pane, and the response in the bottom pane.

How does this work? Well JAX-RPC defines a standard property name, "javax.xml.rpc.service.endpoint.address", that will change the URL the stub will send to request to.
Note that we do NOT use "?WSDL" on this URL as this is the endpoint that gets the request, not the URL of the WSDL document.

What else can you do to the Stub? Well, you can read the Javadocs for Stub class in Axis at http://ws.apache.org/axis/java/apiDocs/org/apache/axis/client/Stub.html
and you will see a few interesting APIs. With the addition of the get/set SOAP Header functions in CFMX7 however, ColdFusion already provides access to almost everything via those functions or tag attributes to cfobject or cfinvoke.

Wednesday, September 21, 2005

Upgrading ColdFusion MX 7 to Axis 1.3

The Axis team is poised to create a 1.3 release of Axis shortly. Before we do that, I run the ColdFusion regression tests on the bits to make sure our customers can upgrade without problems.

Well, there is a problem, but it is due to some changes in Axis and wsdl4j, a jar file that Axis uses to process WSDL. Specifically the QName class used to live in wsdl4j.jar and now it lives in jaxrpc.jar. So this isn't a big deal except that the class changed recently to support prefixes. You don't have to care about what that means, but just know that Axis 1.3 calls a prefix API which if the wrong QName class is already loaded you get this error:

500 javax.xml.namespace.QName.getPrefix()Ljava/lang/String;
javax.xml.namespace.QName.getPrefix()Ljava/lang/String

The problem is that we are picking up the wsdl4j classes from runtime/lib/webservices.jar, the JRun supplied copy of Axis and all of its jar files (including wsdl4j.jar). This hasn't been a problem up until now, because the class moved and changed.

Here is how you fix this. It gets pretty messy so make sure you back up cfmx_bootstrap.jar.

  1. Shut down ColdFusion.
  2. Find the file wwwroot/WEB-INF/lib/cfmx_bootstrap.jar, Make a backup copy of it.
  3. Open the file with WinZip
  4. Edit the file jrun.properties. On the last line add the following package names to the end of the "exceptions line: "javax.xml.namespace.,javax.wsdl." Don't forget the comma and the trailing dots. The line will look like this.

    exceptions=javax.xml.messaging.,javax.xml.namespace.,javax.xml.rpc.,javax.xml.soap.,javax.wsdl.

  5. Save this change back in to cfmx_bootstrap.jar
  6. Start ColdFusion

The Gory Details

What this is doing is configuring CF to load the javax.xml.namespace and javax.wsdl packages using the CF classloader, which looks in cfusion/lib first, which is where we want to get the wsdl4j classes from.

Why does it get these classes from werbservices.jar? If you look at the first setting in jrun.properties, you will see that all the classes starting with "javax." are loaded by the Application Servers classloader. Hence JRun goes and loads its QName class (which is in the javax.xml.namespace package).

Whew.

Tuesday, September 06, 2005

SOAP over XMPP

This is a fun specification: http://www.jabber.org/jeps/jep-0072.html

Most people think web services are all about HTTP, but it has always been the case that there are people who want to send their SOAP via other transports.

Monday, April 04, 2005

Consuming complex Types with ColdFusion

Doug James and Larry Afrin have posted a great set of notes on how to use ColdFusion MX to consume Web Services that contain complex types as arguments to their operations. This is information that should have been in the documentation from the start, but Doug and Larry went the extra mile to pull it all together.

We plan on publishing this information as a tech note and I have passed it along to our documentation folks to incorporate in to future CF docs as well.

Tuesday, January 04, 2005

Why Dataset is a bad idea

Stuart Celarier has a good post on why the Macrosoft Dataset type is not good for web services. As I have posted before, I get asked about this a lot when talking Web Services with ColdFusion MX customers. I always recommend that if you are trying to interoperate, avoid these non-standard data types like the plague.

Friday, October 29, 2004

Converting .NET dataset to ColdFusion Queries

Joe Rinehart has posted on his blog a short UDF that shows how to convert a Dataset to CF queries.
This is something we get asked about all the time: "Do CFMX web services support the Microsoft DataSet object?" I have always recommended that using DataSet is a really good way to screw interop, since it is a Microsoft proprietary data type. The problem with it? The WSDL does not describe the data inside the dataset. There is XML Schema inside the XML on the wire that then describes the types of the data. This requires parsing this Schema at runtime. Something the Axis engine at the heart of the CFMX web services support does not do. Why not? Because it is fairly difficult to do well, slows execution time, and would only be to support this wacky MS specific type.

In any case, I haven't tested Joes implementation, but if you are dealing with a MS specific web service that you have to consume, this should help you out tremendously!