Friday 25 July 2008

Consuming WCF Web Service Using Java Client

[Updated on 2009-06-22] The JSE's native wsimport tool has been added along with custom binding in a more recent post - JAXB Custom Data Binding.

From the previous post I showed how to create a simple web service using WCF and consume it with a windows console application written in C#.

To have the same web services consumed by a Java client, I needed to make the web service WS-I Basic Profile 1.1 compliant so that it becomes portable cross-platforms. To do this, it is simply a matter of following instructions on MSDN. In my case, I modified Web.Config file: in <services> section I changed the wsHttpBinding into basicHttpBinding and removed the identity tags. The resulting snippet:


    
        
        
        
        
    

IntelliJ IDEA

To generate the Java web service client, I created a Java project in IDEA 7.0.2. Then right-click on the src tree to select New -> Web Service Client, then paste in the wsdl url (http://localhost/PromoService.svc?wsdl). Bingo, IDEA generates all the interface and class files, together with the client test code. I guess it is using JAXB under the hood. Adding the bits into the client test code resulted:

import svdemo.PromoService;
import svdemo.PromoInfo;

public class SvdemoClient {
  public static void main(String[] argv) {
      svdemo.IPromoService service = (new PromoService()).getBasicHttpBindingIPromoService();
      //invoke business method
      String keywords = "action";
      if(service.hasPromo(keywords)) {
          PromoInfo promo = service.getFirstPromo(keywords);
          System.out.println("promo:"+promo.getPromoName().getValue()
                  +","+promo.getPromoDateTime());
      }   else {
          System.out.println("nothing");
      }
  }
}

Notice how the service is instantiated (code not as nice as in C# version). Also, the PromoInfo class's members data type are of JAXBElement and XMLGregorianCalender..., which are not as nice as native primitive types. But it works - here is the output:

promo:Batman The Dark Knight,2008-07-30T10:00:00

NetBeans 6.0

I created a Java application project in NetBeans. From the project's source branch, right-clicking will bring up the New -> Web Service Client option. Providing the WSDL URI and selecting JAX-WS style will generate the WS client classes. The results are similar to IDEA, the exact same client code (see above) can be used to test the client. However, it is extremely slow so that I thought NetBeans froze. This happens to almost every step onwards. I eventually gave up on NetBeans. Maybe it is time to upgrade.

CXF

Apache CXF is the 2nd generation XFire. By default, it generates JAXB style binding. It came with wsdl2java utility. To generate the client code from wsdl, I used the following commands:

mkdir d:\projects\svdemocxf
wsdl2java -d d:\projects\svdemocxf -client -ant http://localhost/PromoService.svc?wsdl
I then moved all the source files into a src directory. Then build the project.
D:\projects\svdemocxf>ant build
D:\projects\svdemocxf>ant IPromoServiceClient
Buildfile: build.xml

compile:
    [javac] Compiling 15 source files to D:\projects\romen\build\classes

IPromoServiceClient:
     [java] Invoking hasPromo...
     [java] hasPromo.result=false
     [java] Invoking getFirstPromo...
     [java] getFirstPromo.result=null
     [java] Invoking getKeywordsFromMySpace...
     [java] getKeywordsFromMySpace.result=

BUILD SUCCESSFUL
Total time: 4 seconds

Note that the environment variable CXF_HOME had to be defined and point to the CXF installation directory for the ant build.xml file to work.

I added the client testing code similar to the one above:

package svdemo;
import org.datacontract.schemas._2004._07.svdemo.PromoInfo;
import org.tempuri.*;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  IPromoService service=(new PromoService()).getBasicHttpBindingIPromoService();
  String keywords="action";
  if(service.hasPromo(keywords)) {
   PromoInfo promo=service.getFirstPromo(keywords);
   System.out.println("promo:"+promo.getPromoName().getValue()
     +","+promo.getPromoDateTime());
  } else {
   System.out.println("nothing");
  }
 }
}
To run the test, I added the following target to the build.xml file:

    
 

Running the RunTest target yields:

D:\projects\svdemocxf>ant RunTest
Buildfile: build.xml

compile:
    [javac] Compiling 16 source files to D:\projects\svdemocxf\build\classes

RunTest:
     [java] promo:Batman The Dark Knight,2008-07-30T10:00:00

BUILD SUCCESSFUL
Total time: 4 seconds

Axis2

Apache Axis2 (v1.4) comes with the wsdl2java utility which generates java source code from WSDL file. Using the following command:

wsdl2java.bat -o \projects\svdemoAxis -p svdemoClient -uri http://localhost/PromoService.svc?wsdl

This generates the client stub class PromoServiceStub and dozens of inner classes - one for each input/output parameter and data type! The client test code ends up like this:

package svdemoClient;

import java.rmi.RemoteException;
import org.apache.axis2.AxisFault;
import svdemoClient.PromoServiceStub.PromoInfo;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  try {
   PromoServiceStub service=new PromoServiceStub();
   String keywords = "action";
   
   PromoServiceStub.HasPromo hasPromoPara=new PromoServiceStub.HasPromo();
   hasPromoPara.setKeyword(keywords);
   PromoServiceStub.HasPromoResponse hasPromoResp = service.HasPromo(hasPromoPara);
   if(hasPromoResp.getHasPromoResult()) {
    PromoServiceStub.GetFirstPromo firstPromoPara=new PromoServiceStub.GetFirstPromo();
    firstPromoPara.setKeyword(keywords);
    PromoServiceStub.GetFirstPromoResponse firstPromoResponse = service.GetFirstPromo(firstPromoPara);
    PromoInfo promo = firstPromoResponse.getGetFirstPromoResult();
    System.out.println("promo:"+promo.getPromoName()
      +","+promo.getPromoDateTime().getTime());
   } else {
    System.out.println("nothing");
   }
  } catch (Exception e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
 }

}
Compiling and running it in Eclipse 3.4 yields:
promo:Batman The Dark Knight,Wed Jul 30 10:00:00 EST 2008
As shown above, the code is very UGLY! For each input parameter and output response, a class has to be instantiated (even when the parameter is simply a string). This not only makes the code long, but also very hard to read. This is the ADB binding used by Axis2 by default. Changing the binding style into xmlbeans or jibx slightly improves the esthetic and readability of the code, but not much. The following test client code snippet is written against XMLBeans binding style from Axis:
String keywords = "action";
HasPromoDocument hasPromoDoc = HasPromoDocument.Factory.newInstance();
hasPromoDoc.getHasPromo().setKeyword(keywords);
HasPromoResponseDocument hasPromoResp;
hasPromoResp = service.HasPromo(hasPromoDoc);

if(hasPromoResp!=null && hasPromoResp.getHasPromoResponse().getHasPromoResult()) {
 GetFirstPromoDocument firstPromoDoc=GetFirstPromoDocument.Factory.newInstance();
 firstPromoDoc.getGetFirstPromo().setKeyword(keywords);
 GetFirstPromoResponseDocument firstPromoResp=
  service.GetFirstPromo(firstPromoDoc);
 if(firstPromoResp!=null) {
  PromoInfo promo=firstPromoResp.getGetFirstPromoResponse().getGetFirstPromoResult();
  System.out.println("promo:"+promo.getPromoName()
    +","+promo.getPromoDateTime().getTime());
 }
}
This article from DeveloperWorks gives more insights and examples on the different types of Axis2 bindings. As of Axis2 v1.4, it came with a JAXB experimental support. The above is just my feeble attempt on Axis2. However, Axis2's wsdl2java is quite capable of generating esthetic code - Eclipse 3.4 uses Axis under the hood (using JAXB I guess) to generate web service clients.

Eclipse Ganymede JEE Distribution

Eclipse Ganymede (v3.4) has an option to add new Web Service Client. However, it requires you to specify a Server - this is totally wrong, there is absolutely no need to have a server definition to generate WS client code. However, I just played along and defined a Tomcat 6.0 server. It allows me to continue with the WS Client wizard. Then, after I specified the WSDL URI, it gave me the error:

IWAB0503E Unable to update Java build path. Please check your system environment.
    Java Model Exception: Java Model Status [Build path contains duplicate entry: 'D:tools/eclipse/plugins/org.apache.axis_1.4.0.v200806030120/lib/axis.jar' for project 'svdemoAxis']
    ...
Days later, I upgraded one of the Eclipse plugins (which was auto-detected and suggested by Eclipse), then things were better. Retrying generating the web service client resulted in a new WebServiceProject project being created. I then added the testing client:
package svdemoClient;
import java.rmi.RemoteException;

import org.datacontract.schemas._2004._07.svdemo.PromoInfo;
import org.tempuri.*;

public class Program {

 /**
  * @param args
  */
 public static void main(String[] args) {
  String keywords="action";
  IPromoService service=(new IPromoServiceProxy()).getIPromoService();
  try {
   if(service.hasPromo(keywords)) {
    PromoInfo promo=service.getFirstPromo(keywords);
    System.out.println("promo:"+promo.getPromoName()
      +","+promo.getPromoDateTime().getTime());
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

}
Note that the promotDateTime field generated is of java.util.Calendar type, not XMLCalendar.

Related Posts:

10 comments:

Anonymous said...

That was very useful. Thanks :)

Anonymous said...

Was the J2SE v.6 native web service runtime left out of this list on purpose? As of version 6, Java can consume web services without using any of the listed supporting frameworks. All it takes is to run wsimport (an utility app that's part of the JDK) on the WSDL URL, and use the generated stubs. Also, it takes care of JAXBElement and XMLGrewgorianCalendar elements, too - just specify a custom binding.

Alex

Romen said...

Thanks Alex.

No, it was not intentional. I simply missed it. I added it in a more recent post along with the required customised data binding.

cheers
romen

Anonymous said...

Great work. This post helped me more than any other info out there. Thank you!!

Anonymous said...

Watch this video

Klerisson Paixao said...

Is there any chance to get a Java webservice client running along WCF without moving wsHttpBinding into basicHttpBinding? Did you try Metro Stack? Any clue? Thanks a lot!

Linh Le said...

Can you tell me how to solve with wsHttpBinding. I don't know how to solve this

Unknown said...

i have the same doubt like linh le, because i'm doing a wcf service with WS-RM enabled and wsHttpBinding is almost mandatory. thanks in advance

Unknown said...

Thank you for your contribution here. I am newbie in Java Interface with .NET and looking for porting Java Server Interface to .NET WCF so far no idea at all.
Pls advise me.

Nelson Giraldo said...

Hello, I have 2 endpoints configured in WCF netTcpBinding basicHttpBingind and when I generate the Java reference to me generates error. but if I delete the endpoint netTcpBinding if I generate the reference.
I fix this?.
Thank you.