Wednesday, August 8, 2007

Most common WSDL stumbling block

When it comes to helping people debug WSDL issues, the #1 overlooked and misunderstood problem is usually whether to specify type or element as the attribute to the element. If you are creating web services and defining WSDLs you must understand this significant difference as it will effect the consumers of your services.

All my examples and information where referenced from the book Building Web Services with Java. I would highly recommend this book.

Simple WSDL
Let's first begin with snippets from a simple WSDL.

<definitions name="PriceCheck">
<types>
<xsd:schema>
<xsd:element name="sku" type="xsd:string"/>
<xsd:complexType name="availabilityType"
<xsd:sequence>
<xsd:element ref="sku"/>
<xsd:element name="price" type="xsd:double"/>
<xsd:element name="quantityAvailable" type="xsd:integer"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="StockAvailability" type="availabilityType"/>
</xsd:schema>
</types>
<message name="PriceCheckRequest">
<part name="sku" element="sku"/>
</message>
<message name="PriceCheckResponse">
<part name="result" element="StockAvailability"/>
</message>
<portType>
<operation name="checkPrice">
.......
<binding>
.......
<service>
.......
</definitions>
WSDL Parts
A part element is made up of two properties: the name of the part and the kind of the part. The name property is represented by the name attribute, which must be unique among all the part child elements of the message element. The kind property of the part is defined as either a type attribute (a simpleType or complextType from the XSD schema type system) or an element attribute (also defined by referring to an element defined in the XML schema). You can't use both an element attribute and a type attribute to define the same part.

The choice of using element or type when defining the part is very important. The most common mistake is using the type attribute and referencing an XSD element or using the element attribute and referencing an XSD simpleType or complexType.

Use of element
When you use the element attribute, you're specifying that the SOAP should be precisely that XML element. For example, the PriceCheckRequest example specifies that the XML element named sku must be used as the body of the messsage. Here is a snippet of what the SOAP would look like:

<soap:Envelope>
<soap:Body>
<sku>123</sku>
</soap:Body>
</soap:Envelope>
Note how the SOAP contains exactly the specified sku element. The element attribute should be used to define the type of the part when the Web service is meant to be document oriented (which is defined in the binding). Whenever the element attribute is used, its value must refer to another global XML element defined or imported in an XML schema.

This not only applies to requests but responses as well. For example, here is what the SOAP would look like for PriceCheckResponse:

<soap:Envelope>
<soap:Body>
<StockAvailability>
<sku>123</sku>
<price>100.0</price>
<quantityAvailable>12</quantityAvailable>
</StockAvailability
</soap:Body>
</soap:Envelope>

Use of type
The PriceCheckRequest could have been built differently, using a part defined with the type attribute, like this:

<message name="PriceCheckRequest">
<part name="sku" type="xsd:string"/>
</message>

Here is an example of the SOAP using the RPC-style where checkPrice is the name of the operation defined in the WSDL:

<soap:Envelope>
<soap:Body>
<checkPrice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<sku xsi:type="xsd:string">123</sku>
</checkPrice>
</soap:Body>
</soap:Envelope>
Summary
That is the difference between defining your part as an element or type. The most common style to use is document oriented and therefore the parts must use elements. If you use type in your parts, you need to use the RPC style.

Unfortunately all this isn't well documented. However there is good news. In WSDL 2.0 message and part elements have been removed. Instead you would use the names of the XML element declarations directly in the operations input, output, and fault messages.

14 comments:

Rob said...

Thanks very much, James. This helped me to understand what I'm looking at.

Might you have any comments on using the same name for an element and a complexType? Is this allowed?

I'm struggling with this right now with a WSDL that will import in some places and not in others; NetBeans will not, for example.

Note: Snippet below has angle brackets replaced with parens so they'll post on this blog.

(s:complexType name="EventSubscriptionStatisticsRequest")

(s:sequence)(s:element minOccurs="1" maxOccurs="1" name="EventTypeIds" nillable="false" type="eww:ArrayOfstring")(/s:element)

(s:element minOccurs="1" maxOccurs="1" name="OrganizationId" nillable="false" type="s:string")(/s:element)(s:element minOccurs="1" maxOccurs="1" name="StartDate" nillable="false" type="s:dateTime")(/s:element)(s:element minOccurs="1" maxOccurs="1" name="EndDate" nillable="false" type="s:dateTime")(/s:element)(/s:sequence)


(/s:complexType)


and



(s:element name="EventSubscriptionStatisticsRequest")
(s:complexType)
(s:sequence)(s:element minOccurs="1" maxOccurs="1" name="EventSubscriptionStatisticsRequests" nillable="false" type="eww:ArrayOfEventSubscriptionStatisticsRequest")(/s:element)(/s:sequence)
(/s:complexType)
(/s:element)


[ERROR] A class/interface with the same name "Notifind.MemberCustomFieldSimpleSetRequest" is already in use. Use a class customization to resolve this conflict.

Rob said...

Sorry --- here is the correct error. There were many similar errors.

[ERROR] A class/interface with the same name "Notifind.EventSubscriptionStatisticsRequest" is already in use. Use a class customization to resolve this conflict.

jlorenzen said...

I am not sure if you can use the same name. My assumption would be no and it's not good practice either I think.
Typically the naming convention is to append the word Type to the complexType name.

So in your example,
<complexType name="EventSubscriptionStatisticsRequestType">

And then in your element, you would reference this complexType.

Rob said...

Thank you much for your response. I'm not sure if that code was generated by VisualStudio or what (but I suspect it was). I got the WSDL from an external agency, and am having all kinds of difficulty using it in Java IDEs due to various issues.

jlorenzen said...

I don't doubt it. Netbeans is pretty picky when it comes to importing WSDLs. I think that is one of the main reasons for taking the top-down approach where you nail down a WS-I Basic Profile compliant WSDL. Verses writing code and letting some tool auto-generate a WSDL that you never even look at.
I think netbeans expects/assumes that any WSDL it imports has to be WS-I Basic Profile compliant. Here are some useful tools to help you in this endeavor.
1) WS-I Basic Profile Tools - http://www.ws-i.org/deliverables/workinggroup.aspx?wg=testingtools
2) Eclipse Web Tools - http://www.eclipse.org/webtools/

Hope it helps. WSDL is not very fun land to play in. If you can use REST instead. I plan on blogging alot about it here soon.

nick said...

Hi James, I have read your post and found it very helpful but I am still hitting the annoyign stunmbling block within Netbeans 6.0. The WSDL is here:

https://api.betfair.com/exchange/v5/BFExchangeService.wsdl

The tags seem to use a slightly different convention, but are nonetheless causing the same old errors when parsing in Netbeans.

I think I need to make either an inline or external JAXB customisation file, but I am unsure of how to start. Once this WSDL is parsed I will no longer need to be messing with XML, I assumed this would be the easy part :-(.

One method which is throwing the error is CancelBets. can you see anything obvious which could set me off to a running start?

Thanks in advance

PS I have just ordered the book you reccommended!

jlorenzen said...

@nick,
Not sure how much I can help but I will try.

First I had no problem importing this WSDL into netbeans. I am using netbeans version 6 (specifically 20071026043710). This is a couple months old and I got it from the OpenESB download.

Here is how I imported it. Create a new BPEL Project. Right click on the project and select New --> Other. Then under XML select External WSDL Document(s). Then select From URL and paste your wsdl address.

I then click the validate button in netbeans and it validates fine.

Here are some comments on the wsdl. It appears they are using document literal (verses document-wrapped). Their message parts use the element convention rather than the type convention (hence the document style instead of RPC).

Also note in the binding section each operation defines a soapAction. I am pretty sure most .Net Services might require this header be present in the HTTP Request, so keep that in mind. To find out if its included you can insert Apaches tcpmon and see the request.

I am not sure what your problem is though until you can be more specific. Are you having problems importing the WDSL in netbeans or you can't invoke the service? If so what is the error you are receiving?

Also note, now that you have the wsdl imported its real easy to create tests using soap UI to test with.

Nick said...

Hi james, thanks for your reply. I have tried importing the WSDL using your suggested method and this works fine for use within the BPEL project.

Previously I was attempting to import the WSDL into a Java Application, using the 'new/web service client' feature and pointing at the WSDL URL. This would allow me to drag and drop relevant methods into the code window for use within my Java App. This method is throwing the error which seemed to be related to your post.

As far as I know this is the only way I can directly call the web service methods from within a Java app or servlet.

At this point I am getting this error:

[ERROR] A class/interface with the same name "Notifind.MemberCustomFieldSimpleSetRequest" is already in use. Use a class customization to resolve this conflict.

I know that people using Visual Studio have zero issues with the equivalent import...

To recreate the error, create a new java application, then right click on the project and select new/web service client. Then point at the WSDL address in the URL field.

Unfortunatley this is the only method I have used to create web service clients within netbeans, so my knownledge is somewhat limited.

If you have any ideas on how I can proceed that would be great, thanks ever so much for your time on this.

Nick

Nick said...

Hi james,

To add to this, when you do import the web service client it will prompt you to accept 2 certificates which Netbeans thinks are unsigned. I thought I should warn you about this in case you think I'm trying to do anything untoward on your PC.

The error trail in full from NB is:

wsimport-client-clean-BFExchangeService:
init:
wsimport-init:
wsimport-client-check-BFExchangeService:
wsimport-client-BFExchangeService:
Consider using **depends**/**produces** so that wsimport won't do unnecessary compilation
parsing WSDL...


[ERROR] A class/interface with the same name "org.me.client.CancelBets" is already in use. Use a class customization to resolve this conflict.
line 234 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Relevant to above error) another "CancelBets" is generated from here.
line 1806 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] A class/interface with the same name "org.me.client.UpdateBets" is already in use. Use a class customization to resolve this conflict.
line 402 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Relevant to above error) another "UpdateBets" is generated from here.
line 1866 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] A class/interface with the same name "org.me.client.PlaceBets" is already in use. Use a class customization to resolve this conflict.
line 514 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Relevant to above error) another "PlaceBets" is generated from here.
line 1880 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] Two declarations cause a collision in the ObjectFactory class.
line 1806 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Related to above error) This is the other declaration.
line 234 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] Two declarations cause a collision in the ObjectFactory class.
line 1880 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Related to above error) This is the other declaration.
line 514 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] Two declarations cause a collision in the ObjectFactory class.
line 402 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

[ERROR] (Related to above error) This is the other declaration.
line 1866 of file:/C:/Users/Nicholas%20Moger/Documents/NetBeansProjects/CalculatorWS_Client_Application/xml-resources/web-service-references/BFExchangeService/wsdl/api.betfair.com/exchange/v5/BFExchangeService.wsdl

BUILD SUCCESSFUL (total time: 2 seconds)

jlorenzen said...

Hey no problem about the certificates. I had no problem accepting them.

On calling a web service from within a Java app or servlet you have multiple options.
Here are the things I have done previously.

1) You can use several open source projects to invoke web services. Some that I have used are xfire (now cxf) and OpenESB. It might be more difficult but OpenESB really allows you to decouple yourself from the service. I can help you with this if you want. The easiest path would probably be CXF. Most web service projects like this, including CXF, include a client gen program you can run a wsdl through and it will create all the objects and port classes necessary to call a web service. CXF also supports dynamically calling a service. That method is used for much simplier services. Here is the CXF project: http://incubator.apache.org/cxf/. I have several others I know have used it easily multiple times and it's pretty reliable.

My other choice that I haven't had the chance to use, but really want to because its so freaking easy its silly. But take a look at this: http://docs.codehaus.org/display/GROOVY/Groovy+SOAP. This uses groovy, which you can use in your java app, to invoke services and is dead simple. It also uses CXF/Xfire under the covers.

Hope that helps.

Nick said...

Thanks for this James, I will start looking into these methods right away. You have inspired me to create my own blog, as these are the first ever blog entries I have ever made :-)

I will let you know of my progress soon, thanks again for this.

jlorenzen said...

For me, writing in this blog is about saving others time. When I google for an answer to a problem and I can't find anything out there, then to me I obviously have to blog about it when I find the solution so others don't have to waste their time. Now think if every developer did that, we would have a lot more solutions out there.

Nick said...

Hi james,

Just wanted to post that I have found a solution to my specific issue within Netbeans 6 which allowed me to consume the service in the manner in which I was originally attempting.

Basically once the client is imported (with errors), right-clicking on the service reference, selecting 'edit web service attributes', then selecting the WSDL customisation tab, expand the 'Global' options at the top. Tick the 'Use Default Package' tickbox, then refresh the client and everything works perfectly!

I personally think this tickbox should be present on the actual import dialogue where you enter the WSDL and your preferred package name...

Either way I'm off and running, thanks for all of your help to date :-)

Anonymous said...

Just to expand on the previous comment by Nick: If you don't explicitly enter a package name when importing the WSDL, it will generate them from the WSDL. Doing it this way it won't try to create two classes called CancelBets withing the same package and you won't have this problem. You'll end up with two classes:

com.betfair.publicapi.types.exchange.v5.CancelBets
com.betfair.publicapi.v5.bfexchangeservice.CancelBets