Tuesday, January 5, 2016

Spring-WS - Contract First web services (SOAP) using Spring-WS

https://www.youtube.com/watch?v=bfTmt_pUazU

http://docs.spring.io/spring-ws/docs/2.2.4.RELEASE/reference/htmlsingle/#d5e201

web.xml


<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">

    <display-name>MyCompany HR Holiday Service</display-name>

    <!-- take especial notice of the name of this servlet -->
    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

 Implementing the Endpoint

In Spring-WS, you will implement Endpoints to handle incoming XML messages. An endpoint is typically created by annotating a class with the @Endpoint annotation. In this endpoint class, you will create one or more methods that handle incoming request. The method signatures can be quite flexible: you can include just about any sort of parameter type related to the incoming XML message, as will be explained later.

3.6.1 Handling the XML Message

In this sample application, we are going to use JDom 2 to handle the XML message. We are also using XPath, because it allows us to select particular parts of the XML JDOM tree, without requiring strict schema conformance.
package com.mycompany.hr.ws;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;

import com.mycompany.hr.service.HumanResourceService;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.filter.Filters;
import org.jdom2.xpath.XPathExpression;
import org.jdom2.xpath.XPathFactory;

@Endpoint                                                                                                              (1)
public class HolidayEndpoint {

    private static final String NAMESPACE_URI = "http://mycompany.com/hr/schemas";

    private XPathExpression<Element> startDateExpression;

    private XPathExpression<Element> endDateExpression;

    private XPathExpression<Element> firstNameExpression;

    private XPathExpression<Element> lastNameExpression;

    private HumanResourceService humanResourceService;
                                                                                                                       (2)
    @Autowired
    public HolidayEndpoint(HumanResourceService humanResourceService) throws JDOMException {
        this.humanResourceService = humanResourceService;

        Namespace namespace = Namespace.getNamespace("hr", NAMESPACE_URI);
        XPathFactory xPathFactory = XPathFactory.instance();
        startDateExpression = xPathFactory.compile("//hr:StartDate", Filters.element(), null, namespace);
        endDateExpression = xPathFactory.compile("//hr:EndDate", Filters.element(), null, namespace);
        firstNameExpression = xPathFactory.compile("//hr:FirstName", Filters.element(), null, namespace);
        lastNameExpression = xPathFactory.compile("//hr:LastName", Filters.element(), null, namespace);
    }

    @PayloadRoot(namespace = NAMESPACE_URI, localPart = "HolidayRequest")                                              (3)
    public void handleHolidayRequest(@RequestPayload Element holidayRequest) throws Exception {                        (4)
        Date startDate = parseDate(startDateExpression, holidayRequest);
        Date endDate = parseDate(endDateExpression, holidayRequest);
        String name = firstNameExpression.evaluateFirst(holidayRequest).getText() + " " + lastNameExpression.evaluateFirst(holidayRequest).getText();

        humanResourceService.bookHoliday(startDate, endDate, name);
    }

    private Date parseDate(XPathExpression<Element> expression, Element element) throws ParseException {
        Element result = expression.evaluateFirst(element);
        if (result != null) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            return dateFormat.parse(result.getText());
        } else {
            throw new IllegalArgumentException("Could not evaluate [" + expression + "] on [" + element + "]");
        }
    }

}
1
The HolidayEndpoint is annotated with @Endpoint. This marks the class as a special sort of @Component, suitable for handling XML messages in Spring-WS, and also making it eligible for suitable for component scanning.
2
The HolidayEndpoint requires the HumanResourceService business service to operate, so we inject the dependency via the constructor and annotate it with@Autowired.
Next, we set up XPath expressions using the JDOM2 API. There are four expressions: //hr:StartDate for extracting the <StartDate> text value,//hr:EndDate for extracting the end date and two for extracting the names of the employee.
3
The @PayloadRoot annotation tells Spring-WS that the handleHolidayRequest method is suitable for handling XML messages. The sort of message that this method can handle is indicated by the annotation values, in this case, it can handle XML elements that have the HolidayRequest local part and thehttp://mycompany.com/hr/schemas namespace.
More information about mapping messages to endpoints is provided in the next section.
4
The handleHolidayRequest(..) method is the main handling method method, which gets passed with the <HolidayRequest/> element from the incoming XML message. The @RequestPayload annotation indicates that the holidayRequest parameter should be mapped to the payload of the request message.
We use the XPath expressions to extract the string values from the XML messages, and convert these values to Date objects using a SimpleDateFormat (theparseData method).
With these values, we invoke a method on the business service. Typically, this will result in a database transaction being started, and some records being altered in the database.
Finally, we define a void return type, which indicates to Spring-WS that we do not want to send a response message. If we wanted a response message, we could have returned a JDOM Element that represents the payload of the response message.
Using JDOM is just one of the options to handle the XML: other options include DOM, dom4j, XOM, SAX, and StAX, but also marshalling techniques like JAXB, Castor, XMLBeans, JiBX, and XStream, as is explained in the next chapter. We chose JDOM because it gives us access to the raw XML, and because it is based on classes (not interfaces and factory methods as with W3C DOM and dom4j), which makes the code less verbose. We use XPath because it is less fragile than marshalling technologies: we don't care for strict schema conformance, as long as we can find the dates and the name.
Because we use JDOM, we must add some dependencies to the Maven pom.xml, which is in the root of our project directory. Here is the relevant section of the POM:
<dependencies>
    <dependency>
        <groupId>org.springframework.ws</groupId>
        <artifactId>spring-ws-core</artifactId>
        <version></version>
    </dependency>
    <dependency>
        <groupId>jdom</groupId>
        <artifactId>jdom</artifactId>
        <version>2.0.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1</version>
    </dependency>
</dependencies>
Here is how we would configure these classes in our spring-ws-servlet.xml Spring XML configuration file, by using component scanning. We also instruct Spring-WS to use annotation-driven endpoints, with the <sws:annotation-driven> element.
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

  <context:component-scan base-package="com.mycompany.hr"/>

  <sws:annotation-driven/>

</beans>

3.6.2 Routing the Message to the Endpoint

As part of writing the endpoint, we also used the @PayloadRoot annotation to indicate which sort of messages can be handled by the handleHolidayRequest method. In Spring-WS, this process is the responsibility of an EndpointMapping. Here we route messages based on their content, by using aPayloadRootAnnotationMethodEndpointMapping. The annotation used above:
@PayloadRoot(namespace = "http://mycompany.com/hr/schemas", localPart = "HolidayRequest")
basically means that whenever an XML message is received with the namespace http://mycompany.com/hr/schemas and the HolidayRequest local name, it will be routed to the handleHolidayRequest method. By using the <sws:annotation-driven> element in our configuration, we enable the detection of the@PayloadRoot annotations. It is possible (and quite common) to have multiple, related handling methods in an endpoint, each of them handling different XML messages.
There are also other ways to map endpoints to XML messages, which will be described in the next chapter.

3.6.3 Providing the Service and Stub implementation

Now that we have the Endpoint, we need HumanResourceService and its implementation for use by HolidayEndpoint.
package com.mycompany.hr.service;

import java.util.Date;

public interface HumanResourceService {
    void bookHoliday(Date startDate, Date endDate, String name);
}
For tutorial purposes, we will use a simple stub implementation of the HumanResourceService.
package com.mycompany.hr.service;

import java.util.Date;

import org.springframework.stereotype.Service;

@Service                                                                                                               (1)
public class StubHumanResourceService implements HumanResourceService {
    public void bookHoliday(Date startDate, Date endDate, String name) {
        System.out.println("Booking holiday for [" + startDate + "-" + endDate + "] for [" + name + "] ");
    }
}
1
The StubHumanResourceService is annotated with @Service. This marks the class as a business facade, which makes this a candidate for injection by@Autowired in HolidayEndpoint.

3.7 Publishing the WSDL

Finally, we need to publish the WSDL. As stated in Section 3.4, “Service contract”, we don't need to write a WSDL ourselves; Spring-WS can generate one for us based on some conventions. Here is how we define the generation:
<sws:dynamic-wsdl id="holiday"                                                                                         (1)
    portTypeName="HumanResource"                                                                                       (3)
    locationUri="/holidayService/"                                                                                     (4)
    targetNamespace="http://mycompany.com/hr/definitions">                                                             (5)
  <sws:xsd location="/WEB-INF/hr.xsd"/>                                                                                (2)
</sws:dynamic-wsdl>
1
The id determines the URL where the WSDL can be retrieved. In this case, the id is holiday, which means that the WSDL can be retrieved as holiday.wsdl in the servlet context. The full URL will typically be http://localhost:8080/holidayService/holiday.wsdl.
3
Next, we set the WSDL port type to be HumanResource.
4
We set the location where the service can be reached: /holidayService/. We use a relative URI and we instruct the framework to transform it dynamically to an absolute URI. Hence, if the service is deployed to different contexts we don't have to change the URI manually. For more information, please refer to the section called “Automatic WSDL exposure”
For the location transformation to work, we need to add an init parameter to spring-ws servlet in web.xml:
<init-param>
  <param-name>transformWsdlLocations</param-name>
  <param-value>true</param-value>
</init-param>
5
We define the target namespace for the WSDL definition itself. Setting this attribute is not required. If not set, the WSDL will have the same namespace as the XSD schema.
2
The xsd element refers to the human resource schema we defined in Section 3.3, “Data Contract”. We simply placed the schema in the WEB-INF directory of the application.
You can create a WAR file using mvn install. If you deploy the application (to Tomcat, Jetty, etc.), and point your browser at this location, you will see the generated WSDL. This WSDL is ready to be used by clients, such as soapUI, or other SOAP frameworks.
That concludes this tutorial. The tutorial code can be found in the full distribution of Spring-WS. The next step would be to look at the echo sample application that is part of the distribution. After that, look at the airline sample, which is a bit more complicated, because it uses JAXB, WS-Security, Hibernate, and a transactional service layer. Finally, you can read the rest of the reference documentation.

No comments:

Post a Comment