Wednesday, June 4, 2008

XML Web service – Axis2 client & xml binding


In this example, we are going to invoke webservice and send complex Java objects
as input/output parameters to the web methods, usig Axis2 wsdl2java and 'xmlBeans' binding option.

Xml data binding, converts complex parameters sent as xml messages to pure java objects.
Axis2 mainly supports three types of xml data binding;

ADB
- for axis2 web services only.


XMLBeans
- supports non-axis webservices, with simple configuration, but somehow complex in coding.


JiBX
- supports non-axis webservice, with simple coding, but requires much configuration.


Given the following webservice implementation, utilizing java.util.HashMap & custom object 'Student', as i/o parameters:

package complex;

public class StudentWS {

public StudentWS() {}

public Student selectStudent(String id)
{
Student student;
// student = get Student by id

return student;
}

public HashMap getAllStudents()
{
HashMap students=new HashMap();
// students = retrieve all students from DB
// students.put(student.getId(),stud.getName());

return students;
}

public void setAllStudents(HashMap students)
{
Iterator iter=students.keySet().iterator();
while(iter.hasNext())
{
// id = (String) iter.next();
// name = (String) students.get(id);
// Insert Id and Name into Student
}
}
}

where the Student class implemented at server side looks like this:

public class Student {

private String id;
private String name;

public Student(){}

public String getId()
{
return id;
}

public void setId(String id)
{
this.id=id;
}

public String getName()
{
return name;
}

public void setName(String name)
{
this.name=name;
}
}

having Axis2 installed and configured (as mentioned in the previous post),
we generate the client with xmlBeans binding, using this command:

wsdl2java -uri wsdlUrl -d xmlbeans
-o C:\client\xmlBeanBinding\complex

(Notice the additional option: -d xmlbeans
for xmlbeans binding).

this will generate client stub and the data types generated by axis2 xmlbeans binding,
at the output directory 'C:\client\xmlBeanBinding\complex'

to export client jars, we run ant command at the output directory "C:/Client/xmlBeans/complex",
then include jars in CLASSPATH of your client implementation.

Here's a sample code to invoke different methods of the ws;

import complex.Student;
import org.apache.xml.xml_soap.Item;
import org.apache.xml.xml_soap.Map;
import org.apache.xmlbeans.XmlString;
import complex.StudentWSServiceStub;
import complex.SetAllStudentsDocument;
import complex.SetAllStudentsResponseDocument;
import complex.GetAllStudentsDocument;
import complex.GetAllStudentsResponseDocument;
import complex.SelectStudentDocument;
import complex.SelectStudentResponseDocument;
import java.rmi.RemoteException;

public class Client {

public static void main(String[] args) {
try {

StudentWSServiceStub stub=new StudentWSServiceStub();
//selectStudent(stub);
//getAllStudents(stub);
//setAllStudents(stub);

} catch (RemoteException ex) {
ex.printStackTrace();
}
}

public static void selectStudent(StudentWSServiceStub stub) throws RemoteException
{
// create a msg to invoke selectStudent
SelectStudentDocument msgDoc = SelectStudentDocument.Factory.newInstance();
SelectStudentDocument.SelectStudent msg = msgDoc.addNewSelectStudent();

// set input parameter to "2"
msg.setId("2");

// invoke ws
SelectStudentResponseDocument resp = stub.selectStudent(msgDoc);

// method returns Student type generated by xmlbeans
Student student = resp.getSelectStudentResponse().getSelectStudentReturn();

//Note: to create new Student - Student.Factory.newInstance();

System.out.println("id: "+student.getId());
System.out.println("name: "+student.getName());
}


public static void getAllStudents(StudentWSServiceStub stub) throws RemoteException
{
GetAllStudentsDocument msgDoc = GetAllStudentsDocument.Factory.newInstance();
GetAllStudentsDocument.GetAllStudents msg = msgDoc.addNewGetAllStudents();

GetAllStudentsResponseDocument resp = stub.getAllStudents(msgDoc);

// method returns Map type simulating HashMap
Map students = resp.getGetAllStudentsResponse().getGetAllStudentsReturn();

for(int i=0;i<students.sizeofitemarray();i++)>
{
Item item=students.getItemArray(i);
XmlString id=(XmlString) item.getKey();
XmlString name=(XmlString) item.getValue();

System.out.println("id: "+id.getStringValue());
System.out.println("name: "+name.getStringValue());
}
}

public static void setAllStudents(StudentWSServiceStub stub) throws RemoteException
{
SetAllStudentsDocument msgDoc = SetAllStudentsDocument.Factory.newInstance();
SetAllStudentsDocument.SetAllStudents msg = msgDoc.addNewSetAllStudents();

// set a Map of students as input param
Map students=Map.Factory.newInstance();

// add item to the map
Item student1 = students.addNewItem();

XmlString id1 = XmlString.Factory.newInstance();
id1.setStringValue("1");
student1.setKey(id1);

XmlString name1 = XmlString.Factory.newInstance();
name1.setStringValue("Student One");
student1.setValue(name1);

Item student2=students.addNewItem();

XmlString id2=XmlString.Factory.newInstance();
id2.setStringValue("2");
student2.setKey(id2);

XmlString name2 = XmlString.Factory.newInstance();
name2.setStringValue("Student Two");
student2.setValue(name2);

// assign the students as input to the method
msg.setStudents(students);

// invoke ws
SetAllStudentsResponseDocument resp =
stub.setAllStudents(msgDoc);


System.out.println("Done :)");
}
}


Reference:
http://www.ibm.com/developerworks/webservices/library/ws-java3/

9 comments:

Anonymous said...

Hi,

thanks for this nice tutorial. We used it for our own webservice using XMLBeans and java.util.HashMap as input parameter but still have two questions. Maybe you could help us a little bit. The WSDL file contains the following complex type:

<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="map" nillable="true" type="ax22:Map"/>
</xs:sequence>
</xs:complexType>

Based on this we generated the client classes using "wsdl2java -d xmlbeans". This command also generates a Map interface which contains an inner Factory class. We use this to generate our parameter map like this:
Map parameterMap = Map.Factory.newInstance();

In contrast to this it seems as if you don't use the generated Map Interface but org.apache.xml.xml_soap.Map which is imported in the third line of your example client code. We couldn't determine which jar contains this map and why you use it.

Furthermore we still encounter difficulties with our WSDL file. It would be a great help if you could paste your WSDL file here (probably for other readers as well).

Thanks a lot in advance and once again thanks for this great tutorial,

Jesko

دعاء said...

Hi Jesko,

I found that my generated wsdl is completely different, because it's generated using IBM
Rational App Developer (RAD 6.0) - To support open source products, i'll give a sample
client to retrieve A java.util.Vector; to a webservice generated by netbeans AND/OR eclipse
in a newer post.

=> Regarding your 1st question:

org.apache.xml.xml_soap.Map is found somewhere in ant generated client jar (in my given
sample: StudentWS-test-client.jar)

but it seems i have completely different generated wsdl;
HashMap is described as follows:

<complexType name="Item">
<all>
<element name="key" type="xsd:anyType"/>
<element name="value" type="xsd:anyType"/>
</all>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="item" type="tns2:Item"/>
</sequence>
</complexType>


I had the following configuration on RAD 6.0 for generating the wsdl:
- webservice runtime: IBM Websphere
- server: WAS 6.0
- type: Java bean Webservice
- encoding style: Document/literal


=> Here's the complete wsdl:

<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions targetNamespace="http://complex" xmlns:impl="http://complex"
xmlns:intf="http://complex" xmlns:tns2="http://xml.apache.org/xml-soap"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsi="http://ws-
i.org/profiles/basic/1.1/xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<schema targetNamespace="http://xml.apache.org/xml-soap"
xmlns="http://www.w3.org/2001/XMLSchema" xmlns:impl="http://complex"
xmlns:intf="http://complex" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<complexType name="Item">
<all>
<element name="key" type="xsd:anyType"/>
<element name="value" type="xsd:anyType"/>
</all>
</complexType>
<complexType name="Map">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="item" type="tns2:Item"/>
</sequence>
</complexType>
</schema>
<schema targetNamespace="http://complex" xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:impl="http://complex" xmlns:intf="http://complex"
xmlns:tns2="http://xml.apache.org/xml-soap" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<import namespace="http://xml.apache.org/xml-soap"/>
<element name="setAllStudentsResponse">
<complexType>
<sequence/>
</complexType>
</element>
<element name="getAllStudents">
<complexType>
<sequence/>
</complexType>
</element>
<element name="getAllStudentsResponse">
<complexType>
<sequence>
<element name="getAllStudentsReturn" nillable="true" type="tns2:Map"/>
</sequence>
</complexType>
</element>
<element name="selectStudent">
<complexType>
<sequence>
<element name="id" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</element>
<element name="selectStudentResponse">
<complexType>
<sequence>
<element name="selectStudentReturn" nillable="true" type="impl:Student"/>
</sequence>
</complexType>
</element>
<element name="setAllStudents">
<complexType>
<sequence>
<element name="students" nillable="true" type="tns2:Map"/>
</sequence>
</complexType>
</element>
<complexType name="Student">
<sequence>
<element name="id" nillable="true" type="xsd:string"/>
<element name="name" nillable="true" type="xsd:string"/>
</sequence>
</complexType>
</schema>
</wsdl:types>

<wsdl:message name="selectStudentResponse">

<wsdl:part element="impl:selectStudentResponse" name="parameters"/>

</wsdl:message>

<wsdl:message name="getAllStudentsResponse">

<wsdl:part element="impl:getAllStudentsResponse" name="parameters"/>

</wsdl:message>

<wsdl:message name="selectStudentRequest">

<wsdl:part element="impl:selectStudent" name="parameters"/>

</wsdl:message>

<wsdl:message name="setAllStudentsResponse">

<wsdl:part element="impl:setAllStudentsResponse" name="parameters"/>

</wsdl:message>

<wsdl:message name="getAllStudentsRequest">

<wsdl:part element="impl:getAllStudents" name="parameters"/>

</wsdl:message>

<wsdl:message name="setAllStudentsRequest">

<wsdl:part element="impl:setAllStudents" name="parameters"/>

</wsdl:message>

<wsdl:portType name="StudentWS">

<wsdl:operation name="setAllStudents">

<wsdl:input message="impl:setAllStudentsRequest" name="setAllStudentsRequest"/>

<wsdl:output message="impl:setAllStudentsResponse" name="setAllStudentsResponse"/>

</wsdl:operation>

<wsdl:operation name="getAllStudents">

<wsdl:input message="impl:getAllStudentsRequest" name="getAllStudentsRequest"/>

<wsdl:output message="impl:getAllStudentsResponse" name="getAllStudentsResponse"/>

</wsdl:operation>

<wsdl:operation name="selectStudent">

<wsdl:input message="impl:selectStudentRequest" name="selectStudentRequest"/>

<wsdl:output message="impl:selectStudentResponse" name="selectStudentResponse"/>

</wsdl:operation>

</wsdl:portType>

<wsdl:binding name="StudentWSSoapBinding" type="impl:StudentWS">

<wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

<wsdl:operation name="setAllStudents">

<wsdlsoap:operation soapAction=""/>

<wsdl:input name="setAllStudentsRequest">

<wsdlsoap:body use="literal"/>

</wsdl:input>

<wsdl:output name="setAllStudentsResponse">

<wsdlsoap:body use="literal"/>

</wsdl:output>

</wsdl:operation>

<wsdl:operation name="getAllStudents">

<wsdlsoap:operation soapAction=""/>

<wsdl:input name="getAllStudentsRequest">

<wsdlsoap:body use="literal"/>

</wsdl:input>

<wsdl:output name="getAllStudentsResponse">

<wsdlsoap:body use="literal"/>

</wsdl:output>

</wsdl:operation>

<wsdl:operation name="selectStudent">

<wsdlsoap:operation soapAction=""/>

<wsdl:input name="selectStudentRequest">

<wsdlsoap:body use="literal"/>

</wsdl:input>

<wsdl:output name="selectStudentResponse">

<wsdlsoap:body use="literal"/>

</wsdl:output>

</wsdl:operation>

</wsdl:binding>

<wsdl:service name="StudentWSService">

<wsdl:port binding="impl:StudentWSSoapBinding" name="StudentWS">

<wsdlsoap:address location="http://localhost:9080/blog/services/StudentWS"/>

</wsdl:port>

</wsdl:service>

</wsdl:definitions>



Hope this could help :)
Regards,

Anonymous said...

Hi,

thanks for your reply. Meanwhile we decided to simplify the whole implementation by switching back from XMLBeans to Axis2-ADB, resulting in less effort for generating classes and configurations. We use Open Source Tools (inlcuding Eclipse) for developing Web applications and originally planned to use a HashMap for transferring a set of parameters from A to B (through a Webservice). As it's somewhat difficult to transfer a HashMap using a WebService we finally decided to use two String arrays instead. This works out of the box.

If any reader should have ambitions to do the same just a short hint. Let's say you have a method with the following signature:
String someMethod(String myMessage, String[] keys, String[] values)


Simply map it to a webservice which looks like this (the WSDL file):

<xs:element name="someMethod">
<xs:complexType>

<xs:sequence>
<xs:element minOccurs="0" name="myMessage" nillable="true" type="xs:string"/>
<xs:element maxOccurs="unbounded" minOccurs="0" name="keys" nillable="true" type="xs:string"/>
<xs:element maxOccurs="unbounded" minOccurs="0" name="values" nillable="true" type="xs:string"/>
</xs:sequence>

</xs:complexType>
</xs:element>


If the services.xml and everything else is set up correctly you can finally call the web service like this:

String message="Hello world"
String[] keys=new String[]{"key1", "key2"};
String[] values=new String[]{"value1", "value2"};

opExecute = new QName("http://my.namespace.com", "someMethod");
opExecuteArgs = new Object[] { message, keys, values };
returnTypes = new Class<?>[] { String.class };
response = serviceClient.invokeBlocking(opExecute, opExecuteArgs, returnTypes);


Good luck,
Jesko

دعاء said...

Thanks Jesko for ur note,
you can check my last tutorial;
creating webservice using Netbeans.
(http://think-learning.blogspot.com/2008/07/xml-web-service-axis2-client-xml.html)

Note: i don't know how to access HashMap parameter using XmlBeans (using the new wsdl),
it only worked for java.util.Vector

Thanks & regards

Anonymous said...

Dear sir,

Many thanks for your nice tutorial. It's self-explanatory. But, i got one problem while i working with list. its does not find org.apache.xml.xml_soap.Item, org.apache.xml.xml_soap.Map class.
I have added all jar file that are bundled with axis2.
Could you please help me, which jar i have missed or where can i find that jar file.

Thanking you,
Ismail
====

دعاء said...

Hi Ismail,

Please check my comment (with date July 10, 2008 11:30 AM)
to see how org.apache.xml.xml_soap.Map class is generated, (wsdl is generated using RAD 6.0):

http://think-learning.blogspot.com/2008/06/xml-web-service-axis2-client-xml.html?showComment=1215714600000#c7889821779688196032


also check other reply, to see how this issue is resolved:

http://think-learning.blogspot.com/2008/06/xml-web-service-axis2-client-xml.html?showComment=1215792000000#c4770821783257170229


you can check my tutorial;
creating webservice using Netbeans.
(http://think-learning.blogspot.com/2008/07/xml-web-service-axis2-client-xml.html)

Note: i could not access HashMap parameter using XmlBeans (in Netbeans tutorial),
it only worked for java.util.Vector

Good luck

Arpan said...

Thank you very much.
I copied your WSDL and client code also.
But I am getting compilation issue on the

// method returns Map type simulating HashMap
Map students = resp.getGetAllStudentsResponse()
.getGetAllStudentsReturn();
// Here in for loop Description Resource Path Location Type
The method sizeofitemarray() is undefined for the type Map Client.java /Student1/src/complex line 59 Java Problem

for (int i = 0; i < students.sizeofitemarray(); i++) {

Arpan said...

Actually I have a typical problem.
I have a web service method which accepts the DTO and DTO contains a Map and this map can contain another hashMap as a value. How can we achieve it?

دعاء said...

Hi,

actually i'm so sorry for the late reply ..
But please refer to my comment to Ismail - on this thread.
He had the same issue

Hope it helps.