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.

11 comments:

Anonymous said...

This isn't directly related to this post (although I am dealing with similar issues. But my question is, can I force an Axis FaultCode and FaultString values from my CFC webservice? I made an "error" object, but my client wants an actual FaultString, FaultCode combo. Can I do that?

Tom said...

@pmolaro No, you don't have that kind of close to metal control over the faults you return from ColdFusion web services.

Anonymous said...

I did find out that <cfthrow> will allow you to put a custom message in. You can even get clever and pass in <cfcatch> data like:
<cfthrow message="type=#cfcatch.type#;message=#cfcatch.message#; detail=#cfcatch.detail#;" />

But I still cannot set the error code (it ignores the errorcode attribute of my throw statement). I also can't get CF to not dump the CF stack trace into the soap fault.

For now I am just passing back my own custom object type named "Error".
Maybe CF9 will offer some more control over Web services.

Thx for your response!

Tom said...

@pmolaro - You can turn off the stack trace by turning off the "Robust Exception" setting in the CF administrator -> Debugging & Logging page.

Jim said...

What about enumerated types? Is it possible to create those with components and structure so they look like that in the generated wsdl?

Is there anyway to keep coldfusion from returning the {SoapOperation}Return element in the web service response?

Can you keep out the extras that Coldfusion puts in such as the CFCInvocationException? Thanks.

Unknown said...

@Tom,

I have been having some trouble with consuming web services in ColdFusion 8. Although your 3 blogs on the subject have been very helpful, I am still having difficulty trying to work out how a complex data type is being translated.

I am unable to post the entire wsdl file on a public site, however below is a cut down example of the elements that are causing issues:

<s:element minOccurs="0" name="Data" type="ns:DataEntity" />

<s:complexType name="DataEntity">
<s:attribute name="Status" use="optional">
<s:simpleType>
<s:restriction base="s:string">
<s:enumeration value="Status1" />
<s:enumeration value="Status2" />
</s:restriction>
</s:simpleType>
</s:attribute>
</s:complexType>

After passing the wsdl file through the wsdl2java application, for this section I get 2 files: DataEntity.java and DataEntityStatus.java

DataEntity has a definition of

public DataEntity(com.example.DataEntityStatus status) {
this.status = status;
}


and DataEntityStatus has a definition of

protected DataEntityStatus(java.lang.String value) {
_value_ = value;
_table_.put(_value_,this);
}


In ColdFusion I created the following struct which comes back with the following error:

Code:
DataEntity = structNew(); DataEntity.Status = structNew();
DataEntity.Status.value = 'Status1';

Response:
Error converting CFML arguments to Java classes for web service invocation.
Unable to create web service argument class com.example.DataEntityStatus. Error: java.lang.InstantiationException: com.example.DataEntityStatus. Often this is because the web service defines an abstract complexType as an input to an operation. You must create an actual instance of this type in Java.


However if I change Status to a string I get the error: Web service operation GetData with parameters ... can not be found.

Do you know how I can create these enumerations in ColdFusion?

Anonymous said...

Hi Tom,
I've been reading through your posts on Web Services and complex types in order to crack a problem I am having with a complex typed wsdl.

I am converting a Java web service that I built using Axis 1.1 to ColdFusion (8), but I need to use the original wsdl file, so I specify this in the component using wsdlfile="mywsdlfile.wsdl".

I construct my parameter to pass to the method as a structure - a mixture of strings, longs, floats and dates.

When I try to invoke the ws I am always getting: "could not find deserializer for type".

If i don't use the wsdl (and just let CF figure out using ws.cfc?wsdl - then it works OK - the structure arrives at the service and I can examine it and use the contents. But why would it not work when using a specific wsdl file?

Shaun said...

You are my hero!

Oo Ice Deep oO said...

Hi Tom,

I am still a newbie when it comes to creating and consuming webservices in cf, I have two that I have consumed, but now I am trying to create one. I am trying to find any information I can regarding data validation and trying to figure out how to get cf to implement xsd:restriction. My searches on Google have left me empty handed. Do you know of any resources that could help me and if not will you write a blog on the subject.

Thanks for your time

Tom said...

@Ice Deep - CF is not able to publish a CFC that will have automatically generated WSDL with Schema restrictions in it.

However you can edit a static WSDL file (start with the WSDL generated from by CF) to include the Schema restrictions you want and then add the wsdlfile attribute to the cfcomponent tag.

Then in the CFC method for the operations you can enforce the restrictions in CFML code.

Leon Miller-Out, CTO said...

Seems like there's still some lively discussion on this old post, so I'll add my question.

Has anyone been able to consume a CF web service that passes structs with .NET as the client? I've been going crazy for a couple of days trying to get .NET to see the key/value pairs inside my struct. It gets the struct as an object of type map, but item, which should be a mapItem array, is always undefined. This happens with C# and VB.NET.

I've been googling like mad, but no-one out there seems to have solved this problem before.