Thursday, June 2, 2011

Spring Web Service (sws:annotation-driven)

I was digging around the spring source code to better understand the spring configuration for annotation driven web services. It turns out the one line (sws:annotation-driven) that we add to spring context file abstracts a lot of configuration detail.

Let's start at the top, spring has Bean Document Readers and Name Space Handler's to parse the application context as part of initializing the container. It has multiple readers and support classes to do this, If the code tag  (web-services) is defined in the context, It will be handled by WebServicesNamespaceHandler.

The WebServicesNamespaceHandler registers multitude of other parsers to handle individual code tags (ex: annotation-driven, interceptors etc.).

In case of annotation-driven code tag it is the AnnotationDrivenBeanDefinitionParser. It turns this parser does quite a bit of magic

Register's Following End Point Mapping Beans
  • PayloadRootAnnotationMethodEndpointMapping
  • SoapActionAnnotationMethodEndpointMapping
  • AnnotationActionEndpointMapping
Register's these End Point Adapter Beans

  • DefaultMethodEndpointAdapter
  • MessageContextMethodArgumentResolver
  • XPathParamMethodArgumentResolver
  • SoapMethodArgumentResolver
  • SourcePayloadMethodProcessor

It also selectively registers Pay Load Processors, If those parsers are present in class path of the, It looks for a specific class in the package to verify their presence like this

  • register Dom4jPayloadMethodProcessor if (org.dom4j.Element)
  • register XmlRootElementPayloadMethodProcessor if  (javax.xml.bind.Binder)
  • register JDomPayloadMethodProcessor if (org.jdom.Element)
  • register StaxPayloadMethodArgumentResolver if (javax.xml.stream.XMLInputFactory)
  • register XomPayloadMethodProcessor if (nu.xom.Element)


This makes spring web service configuration rather trivial, But my pet peeve with spring is why not make this explicit in the documentation. I say this because I had few redundant bean configurations in my web service context before I got around to look at the source code to understand all the hidden configuration.



Wednesday, June 1, 2011

Spring Web Service Tutorial

I know there are plenty of tutorials on how to create a spring web service not including the example from spring source. In my experience I had to go through more than one of those to create a spring web service in one of my projects few years ago. I think spring web service documentation is an excellent resource but can be confusing (Since it details multiple options for each of various steps involved and can be overwhelming)

I would like to bring all those steps (excluding security: security needs a blog by itsef) into a single blog. The various steps that are involved in creating a spring web service are
  1. Define the contract (spring prides itself as a contract first web service)
  2. Build Java objects from xsd to marshal/unmarshall xml payload using (jaxb maven plugin)
  3. Setting up spring web service 
  4. Soap eclipse plugin to test
  5. Jsp page to invoke the spring web service
The business problem is a basic crud (add/update/delete) patient with response/exception where necessary.

Tools
  1. Eclipse J2EE (I am using Helios) edition
  2. M2Eclipse Plugin, Maven2 Editor
  3. Rinzo,  XML Editor and XSD generator
  4. Soap UI Eclipse plugin
Contract First

We need to define the contract(XSD), but It is very tedious to write an XSD by hand. I have found rinzo eclipse plugin and thai open source to be very useful in this regard. You can generate xsd from an xml using either one of those.

As as first step to create the contract, I wrote two xml files (patient.xml and response.xml) to capture all the necessary attributes for this use case.

To generate xsd from the xml, open the xml using rinzo xml plugin, if you use right mouse click a context menu will be displayed and one of the option is Generate XSD. If you select that option an XSD will be generated, The generated xsd needs some clean up but, I think it does the job

Pay Load Marshalling/UnMarshalling

Now we have our contract defined, I would like to generate the Java objects to marshal and unmarshal the XML payload. I am using jaxb2 to marshal the payload, maven2 has an excellent jaxb plugin to generate Java objects from XSD schema.

<plugin>
 <groupId>org.jvnet.jaxb2.maven2</groupId>
 <artifactId>maven-jaxb2-plugin</artifactId>
 <executions>
 <execution>
  <phase>install</phase>
  <goals>
    <goal>generate</goal>
  </goals>
  <configuration>
  <schemaDirectory>src/main/resources/schema</schemaDirectory>
  <removeOldOutput>false</removeOldOutput>
  <generateDirectory>src/main/java/</generateDirectory>
  <generatePackage>prasanna.model</generatePackage>
  </configuration>
  </execution>
</executions>
</plugin>

The maven2 configuration above to generate Java Objects from xsd is self explanatory.

I defined the plugin as such It will generate the necessary Java objects from xsd during install phase of a maven build, I tend to execute this plugin once then comment it out in pom.xml once Java objects are generated.  There is an ant implementation available to do the same task If you prefer ant over maven.

The other part of configuration for xml marshaling is in spring context file, It needs a context path property to be set (all the classes in this package are placed in Jaxb context path)
<oxm:jaxb2-marshaller contextPath="prasanna.model" id="jaxbMarshaller"/>


Setting up Web Service
I would be using this sequence diagram that is part of spring documentation to detail the set up of the spring web service. The following actors are involved in handling the spring web service requests
  • Message Dispatcher : MessageDispatcherServlet configured in web.xml will be the front controller/dispatcher of web service requests
  • EndPointMapping: I am using annotation @Endpoint with methods to handle a particular crud web service request, The MessageDispatcher invokes dispatch method to find the end point mappings and also end point interceptors if they are defined
  • EndPointAdapter: I defined DefaultMethodEndpointAdapter in spring context file as End Point Adapter
  • EndPoint:  I created an end point PatientEndPoint with a @Endpoint annotation.
Let's look the End Point, It is a POJO with Endpoint annotation at class level, I have a listing of one method getPatient from the EndPoint, I would like to bring your attention to the Payload Root annotation on getPatient method.

I have captured the spring debug for end point look up. If you look at the highlighted part on the debug, The end point look up string matches the Payload root namespace{local} defined on the getPatient method. If they do not match as for as spring is concerned there is no end point matching for the soap request.

DEBUG [http-8080-1]:endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping.getEndpointInternal()60 - Looking up endpoint for [{http://localhost:8080/springWebService/patient/schemas}getPatientRequest]

@Endpoint
public class PatientEndPoint 
{
@Autowired
private PatientService service;

public PatientEndPoint()
{
}
 
@PayloadRoot(localPart="getPatientRequest", namespace="http://localhost:8080/springWebService/patient/schemas")
@ResponsePayload
public Patient getPatient(@RequestPayload GetPatientRequest getPatientRequest)
{
Patient pat = service.getPatient(getPatientRequest.getPatientId());
return pat;
}

I am not going to discuss rest of the configuration as it is well documented in spring web service tutorial. If you look at the applicationWSContext.xml from downloaded source code. It is easy enough to understand the rest of the configuration.

Soap Fault
I am going to use Patient Not Found business process exception as a SOAP FAULT to provide a appropriate message to the clients of this soap service.

I think spring provide's a very elegant and simple solution for this just define a Java exception POJO with an annotation (@SoapFault) and spring will do the rest. In your patient service when you throw this exception when patient is not found spring will do the rest. If a client requests a patient with invalid patient id service will throw the patient not found exception, then spring will convert this exception to a soap fault and send a response to the client.

@SoapFault(faultCode = FaultCode.SERVER)
public class PatientNotFoundException extends RuntimeException 
{

 private static final long serialVersionUID = -543749936886227L;

 public PatientNotFoundException()
 {
  
 }
 
 public PatientNotFoundException(String id)
 {
  super("Patient with id: "+ id + " is not found ");
 }
}

Interceptors
I found Soap Envelope logging interceptor to be very useful during the development, As it display's the complete soap request and the response that is being sent to the client. The configuration for the interceptor is rather trivial

<sws:interceptors>
<bean class="org.springframework.ws.soap.server.endpoint.interceptor.SoapEnvelopeLoggingInterceptor">
 <property name="logRequest" value="true"></property>
 <property name="logResponse" value="true"></property>
</bean>
</sws:interceptors>

SoapUI
You can use soap eclipse plugin to test your web service. If you have soap ui plugin installed on your eclipse

  • Go to SOAP UI perspective
  • Select Projects ==> New Saop UI Project
  • import the wsdl file 
  • you are ready to test your soap service

You can download the complete source code from here

Steps after you download the source code
  • Open a new workspace
  • Create ==> other ==> Web ==> Dynamic Web Project
  • make sure you follow standard maven directory structure for web app
  • Now select  springWebService ==> import ==> General ==> File System ==> Path to your local down load
  • Run Build ==> will down load the necessary jars
  • Select the project right mouse click ==> context menu maven ==> update project config will add maven to eclipse class path
If you do all the above steps you should be able to run the work space as a web app on Tomcat server (version 5 and above). If you start the server type this url

http://localhost:8080/springWebService

This will open a web page there you have links to test Web Service and url to down load wsdl file