Database

Consuming .NET Web Services in Oracle JDeveloper

Source Code Accompanies This Article. Download It Now.

Max uses Oracle JDeveloper 10.1.2 to examine the problems you must overcome when consuming ATL/.NET web services in Java clients that rely on Apache SOAP.

Max is a software development consultant specializing in C++, .NET development, and code optimization. He is also the author of Enterprise Application Development with Visual C++ .NET 2005 (Charles River Media, 2006). Max can be contacted at fomitchev@adelphia.net.

Interoperability is one of many challenges that developers of distributed network applications face. Software interoperability becomes especially important when different parts of an application are developed using different tools, or when the entire application (or its components) must interface with third-party software. In theory, interoperability is achieved by adhering to industry standards, such as XML, WSDL, and SOAP, at least as far as web services are concerned. In practice, however, standards are often subject to interpretation and divergent implementations that are not fully compatible. In this article, I use Oracle JDeveloper 10.1.2 to examine the problems you must overcome when consuming ATL/.NET web services in Java clients that rely on Apache SOAP.

Creating a Web-Service Proxy

The first step in consuming web services in Java using Oracle JDeveloper is to create a web-service proxy. This is done by creating an empty Application Workspace with an empty project and then adding a Web Service Proxy (located under Business Tier, Web Services) from the New Gallery dialog; see Figure 1. Then you enter "Web Service WSDL URL" and finish the dialog. At this point, Oracle's code-generation wizard generates the source code necessary for consuming the specified web service. If the web service you just consumed is simple, dealing only with primitive types and no SOAP headers, additional effort is not necessary. However, if the web service you are trying to consume is complex and written using .NET or ATL, this is just the beginning of a number of manual chores that you must do to consume the service.

Although JDeveloper-generated web-service stub code implements all web-service methods, you may experience difficulties:

SOAP headers are not supported by default.

SOAP fault detail text is not properly reported.

Output parameters are missing from web-method stubs.

User-defined type (UDT) fields are expected to obey camel capitalization convention (lowercase, with the first letter of words and all letters of acronyms capitalized) regardless of their actual capitalization.

Web methods with return values or output parameters fail due to type-mapping errors.

The reason for the last two issues is because SOAP response messages generated by .NET and ATL Server web services generally lack embedded XSD-type information in output elements. However, Apache SOAP implementation used by Oracle requires type information for every parameter or UDT field in the response message. A similar issue is with zero-size arrays: Apache SOAP expects a zero-dimension array (for example, <return soapenc:arrayType="MyArrayType[0]" />) when the array contains no elements. However, by default .NET and ATL Server web services generate a nonarray-type element with an xsi:nil="1" attribute. Fortunately, all of these issues can be overcome with some modifications (albeit extensive) to the generated code and a bit of hacking of the Apache SOAP implementation; for example, see the UltraMax ATL Server web service and corresponding Java web-service proxy code (available at http://www.ddj.com/ftp/2006/200604/).

Implementing Web Method Call Templates

The first thing you need to do is implement a reusable web method call template to replace wizard-generated code. The template must:

Support accurate type mapping even when XSD type information is missing from the SOAP response message.

Listing One is an example of the InvokeCall template and related helper methods. The InvokeCall method relies on InitializeCall to specify the web method name and prepare the SOAP header with the makeHeader method. The code in the makeHeader method manually constructs SOAP header documents to be transmitted with the web method request. In Listing One, the SOAP header corresponds to this XML structure:

<m_Header>
<m_SessionID></m_SessionID>
</m_Header>

The ReportFault method parses the <SOAP:Fault> section of the SOAP response by examining the <detail> element. The trick here is that if the <detail> element does not contain any child nodes but contains directly embedded detail text, getDetailEntries() returns null. Thus you have to add a dummy child node to <detail> (for example, <info> in the example) to encapsulate the detail message text. This has to be done on the ATL Server web-service side in the Error method (see UltraMaxService.cpp, available at http://www.ddj.com/ftp/2006/200604/). Fortunately, the addition of the dummy <info> child node does not affect .NET clients consuming the ATL Server web service because SoapException.Detail.innerText produces clear text with all child tags removed (which is not, however, the case for legacy VB6 clients consuming the service via the SOAP Toolkit; these would have to strip the <detail> and <info> tags manually).

The method contains code for extracting the returned value of the m_SessionID from the SOAP header in the response message. Manual actions such as this are necessary for all web-method calls that provide return values in SOAP headers in SOAP response messages. In this example, this happens only in the LogOn method (session ID is returned). The SOAP header is automatically transmitted with each web method thanks to the InvokeCall template.

Type Mapping and User-Defined Types

Apache SOAP relies on the BeanSerializer class for serializing UDTs and on specialized serializers for serialization of string, float, and other primitive types. Therefore, it is useful to define the ResetBeanSerializer method, which registers in the SOAPMappingRegistry all UDTs with BeanSerializer. For example:

This example has only one UDT (CUltraMaxUser), but you need to map all of your UDTs this way if you have more than one. Notice that the ResetBeanSerializer method appears in the web-service stub constructor as well as in the InvokeCall template. In the latter, seemingly redundant placing is necessary for clearing SOAPMappingRegistry from any custom type mapping performed for UDTs appearing in output parameters. Listing Two contains code for mapping UDTs and two helper functionsInvokeUdtTemplate and InvokeUdtArrayTemplatewhich simplify implementation of web methods, returning a UDT or an array of UDTs.

When SOAP response messages contain a <return> element that lacks schema information, Apache SOAP fails to deserialize it even though the schema information could have been specified in the web-service WSDL. The workaround is to explicitly instruct SOAPMappingRegistry as to which deserializer to use to deserialize the return element:

An apparent bug in the Apache SOAP implementation requires that the mapTypes method is called twice when UDTs are mappedonce for empty namespace and again for the default web-service namespace.

Another necessary step is to explicitly specify the deserializer for each field of the UDT. This is achieved with the help of the MapType method, which relies on Java Reflection to query UDT properties and pick a deserializer based on the property type, with UDT properties being mapped recursively.

Just as arrays are deserialized using the ArraySerializer, the array element is also mapped using the MapType method (see InvokeUdtArrayTemplate in Listing Two).

With these template implementations of web methods, returning a UDT or an array of UDTs is straightforward:

Unfortunately, more work is needed to overcome another bug in the Apache SOAP implementation. By default, the BeanSerializer class assumes and expects each UDT field to adhere to camel-naming conventions; for example, a field must be named firstName rather than FirstName. Unavoidably, such totally unwarranted expectations cause deserialization errors if fields of UDTs do not follow camel-capitalization conventions. To solve this, you must implement these modifications:

Hack the BeanSerializer source (http://ws.apache.org/soap/) by replacing the case-sensitive field comparison with a case insensitive one, renaming the BeanSerializer class as MyBeanSerializer, and using MyBeanSerializer in place of the BeanSerializer (see the code in the WebServiceClient JDeveloper project; available at http://www.ddj.com/ftp/2006/200604/).

Provide a custom SimpleBeanInfo-derived class for each UDT that does not adhere to the camel-capitalization convention for its properties. (Java Reflection forcibly changes capitalization of noncamel fields and makes them camel like, which causes BeanSerializer to fail finding the actual field in the UDT.)

Listing Three is a custom bean information class for the CUltraMaxUser UDT. A PropertyDescriptor is created for each UDT field with the capitalization of the field name properly preserved.

Handling null Return Values and Zero-Size Arrays

Once type mapping is implemented, the only other hurdle when consuming ATL web services involves null values and zero-size arrays. The ATL Server SOAP handler generates an xsi:nil="1" attribute when the element's value is null, or when the array is null or zero-size. Unfortunately, Apache SOAP fails to deserialize UDTs whose fields contain the xsi:nil="1" attribute and expects zero-size arrays to be represented as XSD arrays with the dimension parameter set to zero (for example, soapenc:arrayType=mytype[0]).

There are two workarounds to this problem. The simplest one is to catch a SOAP exception thrown by the InvokeCall method and see if the FaultCode equals to "SOAP-ENV:Client"; see the InvokeCallEmpty method in Listing Two. The underlying assumption is that a SOAP exception is thrown on the client side only when the expected value is null. However, if a SOAP exception on the client side can be thrown for other reasons, or if you are invoking a web method that returns multiple output array parameters, this exception-catching approach will not work. Why? Because it deprives additional output parameters from their values when a zero-size array is encountered.

An alternative solution is to modify the ATL library by commenting out lines 4754 through 4760 in atlsoap.h (assuming Visual Studio .NET 2003). This tells the GetArrayInformation method to abort when the array dimension is zero. You would then have to recompile the ATL library by executing this command at the command prompt from the vc7\atlmfc\src directory:

nmake /f atlmfc.mak ATL

You must register Visual C++ environment variables first by executing vcvars.bat (located in vc7\bin folder) at the command prompt, then running nmake, and finally copying the generated library files from the newly created vc7\atlmfc\lib\INTEL folder to the parent lib directory.

The last trick is to allocate a zero-size array using GetMemMgr()->Allocate, instead of setting the array pointer to null. Setting it to null would result in an xsi:nil="1" attribute being generated rather than in zero-size array. For example:

Treating Output Parameters

Until now, I have dealt with web methods that return a single value that, as far as Apache SOAP is concerned, are equivalent to web methods with a single output parameter. However, web methods that produce multiple output parameters require special treatment.

For one thing, Java does not support output parameters. Consequently, if a web method requires multiple output parameters, a UDT must be defined on the client side to host the output values. Also, the output parameter value must be extracted manually from the output parameter array and from the return value itself (the first output parameter is always placed in the return value by Apache SOAP); see the GetTrialPeriodSpan method in Listing Four.

Conclusion

Although consumption of reasonably complex ATL/.NET web services in Java using Apache SOAP is a chore, the process can be streamlined using the template helper methods I've presented here.