In both J2EE technology and .NET, it is quite common to share XSD schemas among multiple Web services. In fact, it is one of the best practices to share XML schemas for the sake of modular design and reusability. The XML tag: import
and include
, are used just for this purpose. For example, you can design a schema for a Product
type for a merchandise warehouse, as shown in Listing 5:
Listing 5. A Product type
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace=
"http://catalog.warehouse.com" xmlns:Product="http://catalog.warehouse.com">
<complexType name="Product">
<sequence>
<element name="_name" type="string"></element>
<element name="_int" type="int"></element>
</sequence>
</complexType>
</schema>
The Product
type is qualified under the namespace http://catalog.warehouse.com. It can be imported into the WSDLs for other Web services managing the inventory. Imagine that the order department has an Order
Web service implemented in C#, as shown in Listing 6:
Listing 6. An order Service in .NET
[WebService(Namespace="http://order.warehouse.com/service")]
public class OrderProductService: System.Web.Services.WebService
{
[WebMethod]
[XmlInclude(typeof(Product))]
public string OrderProducts(Product[] products)
{
int len = products.Length;
//do the order
return "Total number of orders processed: " + len;
}
}
The inventory department has an inventory Web service to restock products to its inventory; it must reuse the same Product
type, as shown in Listing 7:
Listing 7. An inventory Service in .NET
[WebService(Namespace="http://inventory.warehouse.com/service")]
public class InventoryProductService: System.Web.Services.WebService
{
[WebMethod]
[XmlInclude(typeof(Product))]
public string RestockProducts(Product[] products)
{
int len = products.Length;
//add to the inventory
return "Total number of products added: " + len;
}
}
Now that there are three namespaces: http://catalog.warehouse.com for the Product
type, the http://order.warehouse.com/service for the Order
Web service and the http://inventory.warehouse.com/service for the Inventory
Web service. At first look, there doesn't seem to be any potential name conflicts. According to the previous two sections: The three namespace URIs are fully qualified; the domain name for each Web service is unique; even the Web services class names are different.
But still, the problem occurs. The problem occurs when arrays are passed, as in the two Web methods in Listing 6 and Listing 7.
If you build a J2EE project to integrate both the order service and the inventory service, and order a list of products or restock a list of products, only one Web service will be correctly executed; the other one will silently fail because of an empty array of products received, even though the J2EE client indeed sends both with a filled array of products. Why?
You have to investigate the SOAP traffic between the J2EE client and the .NET Web services to look for the answer.
Listing 8. The SOAP request for the .NET order service
<soapenv:Body>
<OrderProduct xmlns="http://order.warehouse.com/service">
<products>
<Product>
<_name xmlns="http://catalog.warehouse.com">Computer</_name>
<_qty xmlns="http://catalog.warehouse.com">10</_qty>
</Product>
<Product>
<_name xmlns="http://catalog.warehouse.com">Monitor</_name>
<_qty xmlns="http://catalog.warehouse.com">20</_qty>
</Product>
</products>
</OrderProduct>
</soapenv:Body>
Listing 9. The SOAP request for the .NET inventory service
<soapenv:Body>
<RestockProduct xmlns="http://inventory.warehouse.com/service">
<products>
<Product xmlns="http://order.warehouse.com/service">
<_name xmlns="http://catalog.warehouse.com">Computer</_name>
<_qty xmlns="http://catalog.warehouse.com">10</_qty>
</Product>
<Product xmlns="http://order.warehouse.com/service">
<_name xmlns="http://catalog.warehouse.com">Monitor</_name>
<_qty xmlns="http://catalog.warehouse.com">20</_qty>
</Product>
</products>
</RestockProduct>
</soapenv:Body>
Compare the XML representations of the orders array and the products array in Listing 8 and Listing 9. The array element in the request to the Inventory
service is qualified by the namespace URI of the Order
service -- http://order.warehouse.com/service -- so the Inventory
Web service does not see any product being sent to it.
So, where is the root of the problem? The problem is in the .NET WSDLs and the helper classes that the Application Developer JAX-RPC tools generates. Listing 10 shows the WSDL for the order service in relation to the ArrayOfProduct[]
:
Listing 10. Portion of the WSDL for the .NET Order service in relation to the ArrayOfProduct type
xmlns:s1="http://catalog.warehouse.com"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:s0="http://order.warehouse.com/service"
targetNamespace="http://order.warehouse.com/service"
.......
<types>
<s:schema elementFormDefault="qualified" targetNamespace=
"http://order.warehouse.com/service">
<s:import namespace="http://catalog.warehouse.com" />
.......
<s:element name="Product" type="s1:Product" />
</s:schema>
<s:schema elementFormDefault="qualified" targetNamespace=
"http://catalog.warehouse.com">
<s:import namespace=
"http://order.warehouse.com/service" />
<s:complexType name="ArrayOfProduct">
<s:sequence>
<s:element minOccurs="0" maxOccurs="unbounded" ref="s0:Product" />
</s:sequence>
</s:complexType>
The schema ArrayOfProduct
is under the targetNamespace http://catalog.warehouse.com, but its element references s0:Product
, where s0
is http://order.warehouse.com/service.
Recall how the namespace URI is mapped to the Java package name when the JAX-RPC client stub classes are created; an ArrayOfProduct
class is created under the package com.warehouse.catalog
, and a helper class ArrayOfProduct_Helper
binds its Product
field to the namespace s0
, which is http://order.warehouse.com/service (see Listing 11).
Listing 11. The helper class binds the Product field to the Order Web service namespace
public class ArrayOfProduct_Helper {
// Type metadata
private static com.ibm.ws.webservices.engine.description.TypeDesc typeDesc =
new com.ibm.ws.webservices.engine.description.TypeDesc(ArrayOfProduct.class);
static {
com.ibm.ws.webservices.engine.description.FieldDesc field = new com.ibm.ws.webservices.engine.description.ElementDesc();
field.setFieldName("product");
field.setXmlName(com.ibm.ws.webservices.engine.utils.QNameTable.createQName("http://order.warehouse.com/service", "Product"));
field.setXmlType(com.ibm.ws.webservices.engine.utils.QNameTable.createQName("http://product.warehouse.com", "Product"));
field.setMinOccursIs0(true);
typeDesc.addFieldDesc(field);
};
This is not good news. Both of the clients for the Inventory
and the Order
Web services share the same ArrayOfProduct
class under the package com.warehouse.catalog
, but its class field binds to a particular Web service; in this case it is the Order
service. That's precisely what you can see in the SOAP request message in Listing 7, and this is what is causing the problem.
In this scenario, there is no violation of specifications from either side. There are no ideal solutions. You must first identify the problem, and there are two things you can do to solve this namespace binding conflict:
- When the first client proxy files are created, refactor the
com.warehouse.catalog
to another package name; when the second client proxy files are generated, both of them will have the correct namespace binding. - Use the same techique as shown in Figure 1 when generating the client proxy files; map the
http://catalog.warehouse.com
to a distinct namespace for each Web service.
It is a good practice to share XSD schemas, but Web service programmers must be aware of the potential namespace problems like this one and know how to fix it when it occurs.
Comments