Overview

CxfDemo provides a simple, working example of consuming a .NET WCF web service in Java, using Apache CXF.

Background

In 2014 I started building a simple Android application, to get some experience with design and architecture of applications on mobile platforms. My intention was to build an application which could be configured to interact with the server side using either HTTP/SOAP or REST/JSON, and I focused on getting the SOAP connectivity up and running first.

At the time, since I thought Android code was run on the standard Java virtual machine (and hence able to use any libraries available to standard Java), I experimented with a few different Java libraries for consuming SOAP web services (Apache Axis and JAX-WS being the main ones). I settled on using Apache CXF, as it seemed to be the most comprehensive, and the one most capable of consuming non-CXF web services (in my case a .NET WCF web service). There were a number of CXF tutorials available online, but none which showed the end-to-end process of generating intermediate Java code from the web service WSDL, right through to actually consuming the web service in Java code.

After a reasonable amount of work getting the CXF code up and running and consuming a WCF web service, I discovered that Android does in fact not run on the standard Java virtual machine. Rather, it runs on Google's Dalvik virtual machine which means many parts of the javax.* package (which are required by CXF) are not included/supported in Android. During earlier research into implementing SOAP vs REST/JSON in Android, I had seen articles and forum postings recommending to always use REST with Android applications... now I understood why! SOAP web services can be consumed in Android using the ksoap2-android library, but it doesn't provide automatic serialization and deserialization of complex data types, meaning you have to write a lot of the serialization code yourself.

So, although I couldn't ultimately use CXF for my Android project, I wanted share this example of how CXF can be used to consume .NET web services in standard Java code.

Project

The C# part of the project is a sub set of the server side of my Android application. The application itself is an Android port of Oracle Permission Generator, which I originally released as a Windows Forms application in late 2012. The C# code uses WCF to expose a portion of the data layer of the application as SOAP web services. The main method GetObjects(), returns a list of OracleObjectPermissionSet objects, which are the main container object in Oracle Permission Generator (representing an object in an Oracle database and the user permissions associated with it). In addition a ConvertToUpperCase() method is exposed which takes and returns basic string types, to provide a more simple example.

On the Java side, the Program class consumes both methods using CXF, and displays the results on the console. The GetObjects() method is also called with a null parameter to demonstrate CXF's handling of nulls.

C# Code

CxfDemo.Containers Project

The C# part of the project imports the dlls of the data layer of my original Windows Forms application, and hence uses the same container objects as the original. Most of these original container classes implement the IXmlSerializable interface. .NET does not allow a class to implement IXmlSerializable, and also the use the DataContract attribute used by WCF (see here for an explanation of why). Hence I reimplemented the 2 required container classes in the CxfDemo.Containers project, and used the DataContract attribute on both of them. The main CxfDemo project contains a class ContainerObjectConverter, which is used to convert objects in the OraclePermissionGeneratorDataModel namespace, to the equivalent (reimplemented) CxfDemo.Containers objects.

CxfDemo Project

Interface IOraclePermissionGeneratorWebServiceAPI, and implementing class OraclePermissionGeneratorWebServiceAPI define and implement the actual web service methods. In the case of the GetObjects() method, the actual functionality is provided by an instance of the OraclePermissionGeneratorDataInterfaceModel.OraclePermissionGeneratorDataInterfaceLayer class.

Calls to GetObjects() must pass an AuthenticationContext, which is a simple container object holding a string used to identify the user/consumer of the web service. Additionally calls can optionally include a TrackingData object, which holds the IP address, and physical location (latitude and longitude) of the user/consumer device.

The web service is implemented using the WCF ServiceHost class.

Obtaining and Installing CXF

CXF can be downloaded from its official webpage cxf.apache.org. I used the version 3.0.1 binary distribution for this demonstration. After unzipping the downloaded file, the /bin folder will contain a number of batch files including 'wsdl2java.bat'.

Running the wsdl2java Utility

The wsdl2java utility utility is used to read a WSDL document, and generate java code from it which will allow consuming of the associated web service. The utility has many command line options, however I needed just the following options to get it to create working java classes from the WSDL exposed by .NET...

Option Description
-d the path to write the resulting java classes to
-p the java package to use for the generated code
-compile compiles the generated java code into .class file
(default required parameter) the wsdl document to create code from

Before running the wsdl2java utility, the .NET web service must be started. To do this, first open and build the C# project. The web service can then be started by running the resulting 'CxfDemo.exe' file. The following should be displayed on the console (and the web service can be stopped by pressing Enter)...

Press [Enter] to stop the service.

To run wsdl2java, I used the following command line...

wsdl2java -d [directory to write the resulting java classes to] -p net.alastairwyse.oraclepermissiongeneratorwebserviceapiconsumer -compile http://localhost:5000/OraclePermissionGeneratorWebServiceAPI?wsdl

This should result in 30 java source and compiled class files being created in the specified destination directory. The files should be integrated into the java project that will consume the web service (e.g. using Eclipse, they must be included in the project build path).

Java Code

The code generated by wsdl2java is fully self contained, in the sense that client code consuming the web service only needs to import the package and classes generated by wsdl2java. The client code does not need to import or make reference to the CXF package directly. The Program class included in the Java project includes the necessary import...

import net.alastairwyse.oraclepermissiongeneratorwebserviceapiconsumer.*;

Interface IOraclePermissionGeneratorWebServiceAPI, and class OraclePermissionGeneratorWebServiceAPI provide the actual interface to consume the web service. These are instantiated in the Program class in the following lines of code...

URL serviceUrl = new URL("http://localhost:5000/OraclePermissionGeneratorWebServiceAPI?wsdl"); OraclePermissionGeneratorWebServiceAPI webServiceApi = new OraclePermissionGeneratorWebServiceAPI(serviceUrl); IOraclePermissionGeneratorWebServiceAPI webServiceApiInterface = webServiceApi.getBasicHttpBindingIOraclePermissionGeneratorWebServiceAPI();

Additionally an ObjectFactory class is instantiated. This class is also created by wsdl2java, and allows you to convert between the objects passed to and from the web service as parameters, and XML representations of the same objects used in the SOAP messages which transmit the web service calls.

ObjectFactory jaxbObjectFactory = new ObjectFactory();

The Program class first calls the ConvertToUpperCase() method using the following statement...

String returnedString = webServiceApiInterface.convertToUpperCase("abcdef");

Calling the GetObjects() method is more complicated, as it requires complex container objects to be passed as parameters. The container classes' definitions are created by the wsdl2java utility. Instances of these classes should be created by calls to the appropriate methods on the ObjectFactory, rather than by the new keyword. Properties of the container objects should be set also using the ObjectFactory. The below code sets up the AuthenticationContext and TrackingData container objects passed to the GetObjects() method...

// Use the object factory to setup the input parameters AuthenticationContext authenticationContext = jaxbObjectFactory.createAuthenticationContext(); authenticationContext.setUserIdentifier(jaxbObjectFactory.createAuthenticationContextUserIdentifier("test@tempuri.org")); Location location = jaxbObjectFactory.createLocation(); location.setLatitude(new Double(35.6895)); location.setLongitude(new Double(139.6917)); TrackingData trackingInformation = jaxbObjectFactory.createTrackingData(); trackingInformation.setLocation(jaxbObjectFactory.createLocation(location)); byte[] inetAddress = InetAddress.getLocalHost().getAddress(); trackingInformation.setIpV4Address(jaxbObjectFactory.createTrackingDataIpV4Address(inetAddress));

Finally, GetObjects() is called using the following statement...

ArrayOfOracleObjectPermissionSet result = webServiceApiInterface.getObjects(authenticationContext, trackingInformation);

The ArrayOfOracleObjectPermissionSet returned by the call is then passed to private method PrintToConsole(), where the properties of the OracleObjectPermissionSet objects are written to the console.

Finally the GetObjects() method is called again but a null TrackingData parameter is passed. No special conversion or serializing of null is required here. CXF automatically converts the java null appropriately in the resulting SOAP message.

Running the Program

After building/compiling, the java side code can be executed directly from an IDE (e.g. Eclipse) or run using the following command line...

java -cp "[path containing CxfDemo]\CxfDemo\Java\CxfDemo\lib";"[path containing CxfDemo]\CxfDemo\Java\CxfDemo\bin" net.alastairwyse.cxfdemo.Program

The following should be written to the console...

Result of ConvertToUpperCase() call... ABCDEF Result of GetObjects() call... Object Name: CUSTOMERS Object Type: Table Object Owner: XYZON Role to permission mappings: XYZON_POWER_ROLE : SELECT XYZON_READ_ROLE : SELECT Add Permission: true Remove Permission: false Object Name: ITEMS Object Type: Table Object Owner: XYZON Role to permission mappings: XYZON_POWER_ROLE : SELECT XYZON_READ_ROLE : SELECT Add Permission: true Remove Permission: false Object Name: ORDERS Object Type: Table Object Owner: XYZON Role to permission mappings: XYZON_POWER_ROLE : SELECT XYZON_READ_ROLE : SELECT Add Permission: true Remove Permission: false Object Name: APPLICATION_STATS Object Type: Table Object Owner: XYZON Role to permission mappings: XYZON_POWER_ROLE : SELECT XYZON_READ_ROLE : SELECT XZYON_APP_ROLE : SELECT XZYON_APP_ROLE : INSERT XZYON_APP_ROLE : UPDATE XZYON_APP_ROLE : DELETE Add Permission: true Remove Permission: false (etc...)

On the .NET side, when calls to the web service methods are recieved, the tracking information passed in the TrackingData object is written to the console...

2015-02-07 23:12:05.515 : Method GetObjects() called. Source IP Address: 192.168.2.101 Location longitude: 139.6917 Location latitude: 35.6895 2015-02-07 23:12:05.828 : Method GetObjects() called. Tracking information not received

Troubleshooting

The .NET ServiceHost class by default does not return details of exceptions on the server side to the web service consumers/clients. This caused me a lot of teething problems in getting the java side code setup, as calls to the web service methods always resulted in connection reset exceptions. I assumed the problem was that I had not setup the java side code correctly, or because of an incompatibility between CXF and .NET. That was until I realised that infact the error was because of a simple bug on the .NET side, but rather than return the resulting exception back as a soap fault, the ServiceHost was simply silently closing the TCP connection. This was corrected by setting the following property on the ServiceHost...

// Turn on option to include exception in any web service interface errors myHost.Description.Behaviors.Find<ServiceDebugBehavior>().IncludeExceptionDetailInFaults = true;

If you have problems connecting to the web service, consider using the WCF Test Client application (included with Visual Studio) to confirm that the web service is running correctly...

WCF Test Client

Download

The source code for CxfDemo is available on GitHub.

Contact

Feel free to contact me via the details here with any questions or comments.