Generating code using WSDL
Gene and Francis are the programmers of the group at the newspaper, brought in from the IT department whenever the Classifieds department can pry them away for one of their own projects. They're going to work on generating the WSDL code by approaching it in two ways; Gene will focus on generating code from Java to WSDL, and Francis will generate the code from WSDL to Java.
In the early days of WSDL, two of the first applications to appear were Java2WSDL and WSDL2Java. After all, what good is an automated format if you can't use it to automate anything? Of course, back then your options were somewhat limited. Code was oriented towards the RPC style, and it was difficult to automatically generate a system with complex payloads.
Fast forward to today, and these problems have been pretty well licked. Axis2 can generate Java code from virtually any WSDL document, and WSDL from a Java class. It accomplishes this feat by using data binding, in which an XML structure gets converted to a Java object, and vice versa. The generation process creates the code, which you can then alter, tweak, compile, and run.
First, Gene starts with a Java class and uses it to generate a WSDL document. Francis then takes that document and uses it to generate both the service and client. For the service, the generation process creates a skeleton into which you can add your own code to perform the actions you wish the service to perform. For the client, it creates a stub you can use to call a web service method as though it were a Java method.
The first step is to make sure that your environment is ready. Download version 0.95 of Apache Axis2, unpack it, and make sure all of the *.jar files in the lib directory are on the CLASSPATH.
To run the web service, install Apache Geronimo (if you haven't already) and start it. (See Part 1 for instructions.) Download the Axis2 v0.95 War distribution and copy it to the <GERONIMO_HOME>/deploy directory. Geronimo will deploy Axis2 automatically.
Gene starts with the ClassifiedService class, which he intends to use as the maid service, and also as a means for testing to make sure everything works the way he expects (see Listing 35).
Listing 35. ClassifiedService.java
package org.dailymoon.classifieds;
public class ClassifiedService {
public static int createNewAd(String content, String endDate){
ClassifiedAd newAd = new ClassifiedAd();
newAd.setEnd(endDate);
newAd.setContent(content);
newAd.save();
return 1;
}
public static boolean editExistingAd(ClassifiedAd adToEdit){
//Do stuff with the ad here
return true;
}
public static ClassifiedList getExistingAds(){
ClassifiedAd[] listOfAds = {new ClassifiedAd(), new
ClassifiedAd(), new ClassifiedAd()};
ClassifiedList listToReturn = new ClassifiedList(listOfAds);
return listToReturn;
}
public static void finalizeIssue(String dateToFinalize){
//Don't return anything.
System.out.println(dateToFinalize + " finalized.");
}
public static void main (String args[]){
ClassifiedService.createNewAd(
"Eclipse experts needed. Contact Nick for details.",
"4/21/2006");
ClassifiedAd adToEdit = new ClassifiedAd();
adToEdit.setId(1);
adToEdit.setStart("4/8/2006");
adToEdit.setEnd("4/30/2006");
adToEdit.setContent(
"Geronimo experts needed. Contact Nick for details.");
ClassifiedService.editExistingAd(adToEdit);
ClassifiedList adList = ClassifiedService.getExistingAds();
System.out.println(adList.toString());
}
}
|
The application itself is fairly straightforward. It provides an example of creating a new ad, editing an existing ad, and listing all of the existing ads. It provides basic limitations for the four methods Gene wants to expose, createNewAd, editExistingAd, getExistingAds, and finalizeIssue.
(Make sure to comment out the main method before generating the WSDL. It won't hurt anything, but it will generate extra, unnecessary code.)
The class also refers to two other classes, ClassifiedAd, and ClassifiedList. In order for the generation process to understand how to structure these objects as XML, Gene creates them as separate classes (see Listing 36).
Listing 36. ClassifiedAd.java
package org.dailymoon.classifieds;
public class ClassifiedAd {
private int id;
private String startDate;
private String endDate;
private String content;
public void setId(int newId){
id = newId;
}
public void setStartDate(String newStart){
startDate = newStart;
}
public void setEndDate(String newEnd){
endDate = newEnd;
}
public void setContent(String newContent){
content = newContent;
}
public void save(){
//Save data here
System.out.println("Ad saved.");
}
}
|
Again, the class itself is not complete, but the structure is in place (see Listing 37).
Listing 37. ClassifiedList.java
package org.dailymoon.classifieds;
public class ClassifiedList {
public ClassifiedAd[] listOfAds;
public ClassifiedList(ClassifiedAd[] newListOfAds){
listOfAds = newListOfAds;
}
public ClassifiedAd[] getRawAds(){
return listOfAds;
}
public String toString(){
return "This is a string of results.";
}
}
|
Here Gene specifies that the ClassifiedList consists of an array of ClassifiedAd objects.
With all the classes in place, he can generate the WSDL.
Generating the WSDL is a straightforward process. From the command line, Gene issues the command, as in Listing 38:
Listing 38. Command to generate the WSDL
java org.apache.axis2.wsdl.Java2WSDL -cn
org.dailymoon.classifieds.ClassifiedService -o
|
(Note that this should all appear on one line.)
The -cn switch specifies the class that forms the basis for the service. The -o switch specifies the output directory. Assuming there are no problems, the class quietly executes, leaving the ClassifiedService.wsdl file in the output directory. This file is very similar to the one Larry generated earlier -- by design, as they're all working on the same service -- but some small changes need to be made to accommodate items that were generically named by the generation process. Specifically, parameters do not always translate well, and will likely have to be renamed.
Here is the generated WSDL file, with tweaks in bold (see Listing 39).
Listing 39. The WSDL file
<wsdl:definitions xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:tns="http://ws.apache.org/axis2"
xmlns:ns1="http://org.apache.axis2/xsd"
targetNamespace="http://ws.apache.org/axis2">
<wsdl:types>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://org.apache.axis2/xsd"
elementFormDefault="unqualified"
attributeFormDefault="unqualified">
<xs:element type="ns1:ClassifiedAd" name="ClassifiedAd" />
<xs:complexType name="ClassifiedAd">
<xs:sequence>
<xs:element type="xs:int" name="id" />
<xs:element type="xs:string" name="content" />
<xs:element type="xs:string" name="endDate" />
<xs:element type="xs:string" name="startDate" />
</xs:sequence>
</xs:complexType>
<xs:element type="ns1:ClassifiedList" name="ClassifiedList" />
<xs:complexType name="ClassifiedList">
<xs:sequence>
<xs:element minOccurs="0" type="ns1:ClassifiedAd"
name="ClassifiedAd"
maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<xs:element name="createNewAdRequest">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="content" />
<xs:element type="xs:string" name="endDate" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="createNewAdResponse">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:int" name="newAdId" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="editExistingAdRequest">
<xs:complexType>
<xs:sequence>
<xs:element type="ns1:ClassifiedAd"
name="existingAd" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="editExistingAdResponse">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:boolean" name="
wasSuccessful"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="getExistingAdsRequest">
<xs:complexType />
</xs:element>
<xs:element name="getExistingAdsResponse">
<xs:complexType>
<xs:sequence>
<xs:element type="ns1:ClassifiedList"
name="ClassifiedList" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="finalizeIssueRequest">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="issueToFinalize"
/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</wsdl:types>
<wsdl:message name="createNewAdRequestMessage">
<wsdl:part name="part1" element="ns1:createNewAdRequest" />
</wsdl:message>
<wsdl:message name="createNewAdResponseMessage">
<wsdl:part name="part1" element="ns1:createNewAdResponse" />
</wsdl:message>
<wsdl:message name="getExistingAdsResponseMessage">
<wsdl:part name="part1" element="ns1:getExistingAdsResponse" />
</wsdl:message>
<wsdl:message name="editExistingAdRequestMessage">
<wsdl:part name="part1" element="ns1:editExistingAdRequest" />
</wsdl:message>
<wsdl:message name="getExistingAdsRequestMessage">
<wsdl:part name="part1" element="ns1:getExistingAdsRequest" />
</wsdl:message>
<wsdl:message name="editExistingAdResponseMessage">
<wsdl:part name="part1" element="ns1:editExistingAdResponse" />
</wsdl:message>
<wsdl:message name="finalizeIssueRequestMessage">
<wsdl:part name="part1" element="ns1:finalizeIssueRequest" />
</wsdl:message>
<wsdl:portType name="ClassifiedServicePortType">
<wsdl:operation name="finalizeIssue">
<wsdl:input message="tns:finalizeIssueRequestMessage" />
</wsdl:operation>
<wsdl:operation name="createNewAd">
<wsdl:input message="tns:createNewAdRequestMessage" />
<wsdl:output message="tns:createNewAdResponseMessage" />
</wsdl:operation>
<wsdl:operation name="editExistingAd">
<wsdl:input message="tns:editExistingAdRequestMessage" />
<wsdl:output message="tns:editExistingAdResponseMessage" />
</wsdl:operation>
<wsdl:operation name="getExistingAds">
<wsdl:input message="tns:getExistingAdsRequestMessage" />
<wsdl:output message="tns:getExistingAdsResponseMessage" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="ClassifiedServiceBinding"
type="tns:ClassifiedServicePortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document" />
<wsdl:operation name="createNewAd">
<soap:operation soapAction="createNewAd" style="document" />
<wsdl:input>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="finalizeIssue">
<soap:operation soapAction="finalizeIssue" style="document" />
<wsdl:input>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:input>
</wsdl:operation>
<wsdl:operation name="editExistingAd">
<soap:operation soapAction="editExistingAd" style="document" />
<wsdl:input>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getExistingAds">
<soap:operation soapAction="getExistingAds" style="document" />
<wsdl:input>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal"
namespace="http://daily-moon.com/classifieds" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="ClassifiedService">
<wsdl:port name="ClassifiedServicePort"
binding="tns:ClassifiedServiceBinding">
<soap:address location=
"http://127.0.0.1:8080/axis2/services/ClassifiedService" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
|
Most of these changes are just convenience, as well as usability; it's much easier to remember "content" then "param0". Two of these changes -- the namespace at the top and the namespace prefix at the bottom -- are due to slight bugs in the generation process as it exists in Axis2 version 0.95, and by the time you read this, they may no longer be necessary.
Generate the service from the WSDL
Once the WSDL file exists, Francis can use it to generate the service and the client. (Actually, Francis could just as easily have used the version generated by Larry.)
Francis starts by generating the server-side code, as in Listing 40:
Listing 40. The server-side code
java org.apache.axis2.wsdl.WSDL2Java -uri ClassifiedService.wsdl
-ss -sd -p org.dailymoon.classifieds -d xmlbeans -o service
|
(Again, enter this command on a single line.)
The first parameter is the URL for the WSDL file. Yes, you can access a remote file using this application. The second switch, -ss, tells the application to generate the service, as opposed to the client. The -sd switch tells the application to generate the XML service descriptor, making it easier for you to deploy the service once you've generated it. The next parameter is, of course, the package, followed by the data binding method. Available methods are adb, xmlbeans, and jaxme. Finally, to keep things clean, Francis generates the service into a new directory called source.
The result is literally hundreds of files. Fortunately, you only need to deal with one of them.
Although in this case the service has been generated from a WSDL file that was itself generated from a Java class, there is no actual logic in the generated code. Only the structure appears. To get the service to actually do something, you need to edit the skeleton file.
In this structure, the file in question is shown in Listing 41:
Listing 41. File generated from a Java class
service\src\org\dailymoon\classifieds\ClassifiedServicePortTypeSkeleton.
java
|
The code is heavily commented, which is useful when you try to figure it out for yourself, but can be very distracting when you're trying to explain it. Here is the cleaned up version, along with the added code to implement part of the service (see Listing 42).
Listing 42. ClassifiedServicePortTypeSkeleton.java
package org.dailymoon.classifieds;
public class ClassifiedServicePortTypeSkeleton {
public axis2.apache.org.xsd.CreateNewAdResponseDocument
createNewAd
(axis2.apache.org.xsd.CreateNewAdRequestDocument param0 )
throws Exception {
//Todo fill this with the necessary business logic
//throw new java.lang.UnsupportedOperationException();
System.out.println("New ad requested, to end on " +
param0.getCreateNewAdRequest().getEndDate());
System.out.println(
param0.getCreateNewAdRequest().getContent());
axis2.apache.org.xsd.CreateNewAdResponseDocument
responseDoc =
axis2.apache.org.xsd.CreateNewAdResponseDocument
.Factory.newInstance();
axis2.apache.org.xsd.CreateNewAdResponseDocument
.CreateNewAdResponse response =
responseDoc.addNewCreateNewAdResponse();
response.setNewAdId(1138);
return responseDoc;
}
public void finalizeIssue
(axis2.apache.org.xsd.FinalizeIssueRequestDocument param2)
throws Exception {
//Todo fill this with the necessary business logic
}
public axis2.apache.org.xsd.EditExistingAdResponseDocument
editExistingAd
(axis2.apache.org.xsd.EditExistingAdRequestDocument param3)
throws Exception {
//Todo fill this with the necessary business logic
throw new java.lang.UnsupportedOperationException();
}
public axis2.apache.org.xsd.GetExistingAdsResponseDocument
getExistingAds
(axis2.apache.org.xsd.GetExistingAdsRequestDocument param5)
throws Exception {
//Todo fill this with the necessary business logic
throw new java.lang.UnsupportedOperationException();
}
}
|
Each method starts life throwing an UnsupportedOperationException, until you actually implement the method. To get at the data submitted to the service, start with the parameter and get the request itself. From there, you can extract individual members using getter methods.
Obviously, in a real service, you'd want to do more then simply output text, but Francis is just interested in making sure it's working. To create the response, start with the appropriate response document, acquiring an instance through the class's Factory. (The classes themselves are quite complex, containing a number of inner classes, but it's worth having a look to see how they're structured.) Once you have the document, create the actual response itself and add it to that document.
Using setter methods, you can directly set values on the response. Simply return of the response document, and the support classes will handle sending it back to the requester.
To deploy the service, you will need to compile it and turn it into an Axis2 archive file. Start by compiling and packaging the service, as shown in Listing 43.
Listing 43. Package the service
set ANT_HOME=e:\apache-ant-1.6.5
PATH=%PATH%;%ANT_HOME%\bin;
set AXIS2_HOME=e:\axis2
cd service
ant jar.service
|
Adjust your syntax appropriately for non-Windows installations, and be sure to use your actual file locations.
This Ant task compiles all of the appropriate files, and creates two archive files, ClassifiedService.aar, and XBeans-packaged.jar, both in the build/lib directory.
To deploy the service, make sure Geronimo is running and point your browser as shown in Listing 44:
Listing 44. Deploying the service
http://localhost:8080/axis2/Login.jsp |
Log in with the credentials admin/axis2 and click Upload Service>Browse. Navigate to the ClassifiedService.aar file and click OK. Click Upload to complete the process.
If you click View Services, you should see the new service listed.
Generate the client stub from the WSDL
All that's left now is to generate the client to access the new service. To do that, execute the following command from the command line:
Listing 45. Command to generate the client
java org.apache.axis2.wsdl.WSDL2Java -uri ClassifiedService.wsdl -p
org.dailymoon.classifieds -d xmlbeans -o client
|
As usual, this is a single command, meant for a single line. The parameters are virtually identical to those used for the server-side generation, except that you don't need a service descriptor. Also, to keep things clean, Francis puts the new files in a separate, client, directory.
This class should execute quietly, leaving hundreds of files in its wake, but you don't need to deal with any of them directly.
The code generation process doesn't actually create a client, but it does create a class you can use to easily create a client. To simplify compilation, Francis creates a new class file called Client.java in the client\src\org\dailymoon\classifieds directory. This way, Ant will pick up the .java file and compile it with the rest of the source.
Francis adds the code in Listing 46.
Listing 46. The client
package org.dailymoon.classifieds;
import axis2.apache.org.xsd.*;
public class Client{
public static void main(java.lang.String args[]){
try{
ClassifiedServicePortTypeStub stub =
new ClassifiedServicePortTypeStub(null,
"http://localhost:8080/axis2/services/ClassifiedService");
CreateNewAdRequestDocument cnaDoc =
CreateNewAdRequestDocument.Factory.newInstance();
CreateNewAdRequestDocument.CreateNewAdRequest cnaReq =
cnaDoc.addNewCreateNewAdRequest();
cnaReq.setContent("Vintage 1963 T-Bird...");
cnaReq.setEndDate("7/4/06");
CreateNewAdResponseDocument cnaResDoc =
stub.createNewAd(cnaDoc);
System.out.println("New ad ID number: "+
cnaResDoc.getCreateNewAdResponse().getNewAdId());
} catch(Exception e){
e.printStackTrace();
}
}
}
|
The ClassifiedServicePortTypeStub class represents the actual service, and you instantiate it with the AXIS_HOME (here left to the default) and the location of the actual service. Next, create the request document, once again by referencing its Factory, and use it to create a new CreateNewAdRequest, adding it to the request document in the process. Just as in the service itself, you can then set attributes directly using setter methods.
To get the response, use the stub to execute the createNewAd() method, passing it the request document as a parameter. Once you have the response document, or rather the CreateNewAtResponseDocument, you can use it to extract the response itself, and an attribute of that response.
Now let's run it.
To run a client, Francis first needs to compile it. Execute the following steps (see Listing 47).
Listing 47. Compiling the client
>>set ANT_HOME=e:\apache-ant-1.6.5
>>PATH=%PATH%;%ANT_HOME%\bin;
>>set AXIS2_HOME=e:\axis2
>>cd client
>>ant jar.client
Buildfile: build.xml
init:
pre.compile.test:
[echo] Stax Availability= true
[echo] Axis2 Availability= true
compile.src:
compile.test:
jar.client:
BUILD SUCCESSFUL
Total time: 2 seconds
|
First, make sure the environment has the appropriate variables. (This assumes you have already added all of the AXIS2_HOME\lib jar files.) Next, change to the client directory (or whatever directory uses the output for the generation process) and run Ant against the jar.client target. You should see results similar to those shown in italics (see Listing 47). To run the client, first amend the CLASSPATH to include the resources directory and the directory that houses all of the classes created by the data binding process (see Listing 48).
Listing 48. Running the client
>>set CLASSPATH=E:\WSDLFiles\client\resources\;E:\WSDLFiles\client\bui
ld\classes\axis2\apache\org\xsd\;%CLASSPATH%
>>cd build\classes
>>java org.dailymoon.classifieds.Client
|
You should see results like that shown in Listing 49:
Listing 49. New ad ID number
New ad ID number: 1138
|
That, as they say, is all there is to it.





