This is a walkthrough of my first experience with the Java API for XML Web Services 2.0 (JAX-WS 2.0).
My objective was to create a web service with a single method that returns the groups to which a member belongs. JAX-WS gives you two basic ways to build a web service:
- Write a WSDL and use JAX-WS to generate a Java interface for you to implement.
- Write a set of Java classes and use annotations to control how JAX-WS maps your code to a web service.
I opted for the second approach. It was appealing to think that I could just write some code and turn it into a web service simply by adding some annotation.
I expected my web service to receive requests that look like this:
<?xml version="1.0" ?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://xocoatl.blogspot.com/schema/1/"> <soapenv:Body> <ns1:MemberGroups> <MemberID>2</MemberID> </ns1:MemberGroups> </soapenv:Body> </soapenv:Envelope>
I expected my web service to send responses that look like this:
<?xml version="1.0" ?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:ns1="http://xocoatl.blogspot.com/schema/1/"> <soapenv:Body> <ns1:MemberGroupsResponse> <MemberGroup> <ID>1</ID> <name>Orcs</name> </MemberGroup> <MemberGroup> <ID>2</ID> <name>Elves</name> </MemberGroup> </ns1:MemberGroupsResponse> </soapenv:Body> </soapenv:Envelope>
To complicate matters slightly, I wanted to run the web service in Resin, rather than Tomcat or the Sun Java Application Server.
My development environment includes:
- A Windows XP laptop
- cygwin and Xemacs
- JDK 1.5
- ant
I do not use an IDE, so I needed to be able to code my web service in emacs and deploy it from the command line.
Getting Started
I downloaded the JAX-WS 2.0 reference implementation and expanded it.
The samples
directory contains a number of good
examples. I was particularly interested the fromjava-*
examples, including supplychain
.
The documentation does not explain how to run the fromjava samples from the command line. Here is what I figured out:
- Set JAXWS_HOME to the JAX-WS installation directory.
- Go to the
samples/fromjava
directory. - Build and run the web service using the
server-j2se
task:$ ant server-j2se Buildfile: build.xml Trying to override old definition of task apt setup: server-j2se: clean: [delete] Deleting directory C:\Docs\jaxws-ri\samples\fromjava\build setup: [mkdir] Created dir: C:\Docs\jaxws-ri\samples\fromjava\build [mkdir] Created dir: C:\Docs\jaxws-ri\samples\fromjava\build\classes [mkdir] Created dir: C:\Docs\jaxws-ri\samples\fromjava\build\war build-server-java: [apt] warning: Annotation types without processors: [javax.xml.bind.annotation.XmlRootElement, javax.xml.bind.annotation.XmlAccessorType, javax.xml.bind.annotation.XmlType, javax.xml.bind.annotation.XmlElement] [apt] 1 warning [echo] Starting endpoint... To stop: ant server-j2se-stop BUILD SUCCESSFUL Total time: 6 seconds $
The service runs fine despite the annotation warning, but it exits immediately. To work around this I simply put the main thread to sleep for a few minutes after starting the server:
public class AddWebservice { public static void main (String[] args) throws Exception { Endpoint endpoint = Endpoint.publish ( "http://localhost:8080/jaxws-fromjava/addnumbers", new AddNumbersImpl ()); // Stops the endpoint if it receives request http://localhost:9090/stop new EndpointStopper(9090, endpoint); Thread.sleep(60000); // Addded to keep the JVM from exiting } }
- Start another shell to run the client.
- First test the server by requesting the WSDL:
$ wget http://localhost:8080/jaxws-fromjava/addnumbers?wsdl
- Now build and run the client:
$ ant client run
The client is built by requesting the WSDL from the server and then generating a corresponding set of JavaBean classes for working with requests and responses.
Once the sample seemed to be running, I used the
tcpmon
tool from the Apache Axis project to intercept
and read the actual SOAP messages between server and client:
- Download and run the
tcpmon
:$ java -cp axis.jar org.apache.axis.utils.tcpmon 8081 localhost 8080
- Go to the
samples/fromjava/etc
directory and change the port from 8080 to 8081 inbuild.properties
,custom-client.xml
andcustom-schema.xml
. - Make sure the server is still running and then run the client again.
Implementing the Service
The supplychain
sample seemed closest to what I
wanted, so I copied that project as the basis for my experiment.
I was not able to get the SOAP messages to come out the way I
wanted by modifying the code and annotations
from the supplychain
sample.
Finally I created a new project and imported the WSDL for the Amazon Simple Queue Service, since it has messages with the desired structure.
By studying the code generated from the Amazon SQS WSDL I was ultimately able to get what I wanted with only a few lines of code:
package member.server; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebParam.Mode; import javax.jws.WebService; import javax.xml.ws.Holder; import javax.xml.ws.RequestWrapper; import javax.xml.ws.ResponseWrapper; @WebService(name="MemberService", targetNamespace = "http://xocoatl.blogspot.com/schema/1/") public class MemberService { private static final long INVALID_ID = 1L; @WebMethod(operationName = "MemberGroups", action = "MemberGroups") public void getMembers(@WebParam(name = "MemberID") BigInteger memberID, @WebParam(name = "MemberGroup", mode = Mode.OUT) Holder<List<MemberGroup>> memberGroups) throws InvalidMemberIDException { if (memberID.equals(BigInteger.valueOf(INVALID_ID))) { throw new InvalidMemberIDException("Invalid member ID"); } memberGroups.value = new ArrayList(); memberGroups.value.add(new MemberGroup(1, "Steelers")); memberGroups.value.add(new MemberGroup(1, "49ers")); } }
MemberGroup
is a simple unadorned JavaBean:
package member.server; public class MemberGroup { private String name; private Integer id; public MemberGroup() {} public MemberGroup(Integer id, String name) { setID(id); setName(name); } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getID() { return id; } public void setID(Integer id) { this.id = id; } }
Note that the service method returns void
and has an
OUT
parameter.
Deploying to Resin
For the final step, I generated the war file containing the service implementation:
$ ant clean build-server-java create-war
I copied the resulting war file to a fresh Resin instance with an
empty webapps
directory, and modified the server
configuration to run my service as the root application:
<host id="" root-directory="."> <web-app id="/" document-directory="webapps/jaxws-member"/> </host>
After a series of NoClassDefFoundError
exceptions, I
determined that the following jar files must be copied to the server
lib
directory:
- jaxws-api.jar
- jaxws-rt.jar
- jsr173-api.jar
- jsr181-api.jar
- jsr250-api.jar
- resolver.jar
- saaj-api.jar
- saaj-impl.jar
- jaxb-api.jar
- jaxb-impl.jar
Once that was sorted out, the web service ran fine.
1 comment:
Hi,
This article really helps!! I successfully deploy the "fromjava" example on Resin. However I got one error while sending a request from client:
run:
[java] Invoking addNumbers(10, 20)
[java] Exception in thread "main" XML reader error: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[1,1]
[java] Message: Premature end of file.
................
Have any idea about this??
Thank you,
Alex
Post a Comment