CHAPTER 14

images

Web Services

Web services have emerged as a key technology for communication between web-based applications over the network. Web services typically leverage HTTP protocol for communication over the network, and use XML as the message payload format. Using web-related standards allows for application interoperability even between disparate applications and technologies. This chapter will focus on Spring Integration’s support for Standard Object Access Protocol (SOAP) services using the Web Services Definition Language (WSDL) as a method of describing the service for external clients. Spring Integration’s support for REST services will be covered in Chapter 18.

We will be taking a contract-first approach for creating web services using the document/literal style of SOAP messaging. This allows a web service to take advantage of the best of messaging and statelessness. The request-and-response XML message will be defined by an XML schema, and the web service will be based on this, adding the SOAP protocol. This allows the web services consumers and providers to be in any technology, since XML is a text-based messaging format. The SOAP RPC style is typically based on building a web services endpoint based on Java method call. Thus, SOAP RPC supports the concept of a method to be called and parameters to be passed. This causes SOAP RPC to be restrictive and not map well into a messaging framework.

Spring Integration provides two components for supporting web services: invoking a web service by sending a message to a channel using an outbound web service gateway, and sending a message to a channel upon receiving a web service invocation using an inbound web service gateway. Both the inbound and outbound gateways include support for adding an XML marshaller. We will cover how Spring Integration provides both client and server support for web services, and how to integrate web services with the Spring Integration messaging framework.

Maven Dependencies

Spring Integration builds upon the Spring Web Services project (http://static.springsource.org/spring-ws/sites/1.5). The required Maven dependencies for the Spring Integration web services gateways are shown in Listing 14–1. The cglib library is required to support Java configuration, as discussed in Chapter 3.

Listing 14–1. Required Project Dependencies That Enable Spring WS pom.xml

  <dependencies>
    <dependency>
      <groupId>org.springframework.integration</groupId>
      <artifactId>spring-integration-xml</artifactId>
      <version>2.0.1.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.integration</groupId>
      <artifactId>spring-integration-ws</artifactId>
      <version>2.0.1.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2</version>
    </dependency>
  </dependencies>

XML Schema

The standard message payload format for a web service is XML. XML is a plain-text format designed for data transfer and can be read by most application technologies. To ensure that both the web services client and server agree to the message payload format, a contract is usually enforced for the XML data using an XML schema. An XML schema is a description of the XML documents in terms of the constraints on the structure and the content.

To demonstrate, we’ll create an XML schema to represent a ticket request and response of a web service endpoint representing a ticket-issuing system, similar to the examples used in previous chapters. The XML schema describes a ticket request with a TicketRequest element consisting of a description and priority property. The ticket response is represented by a TicketResponse element with the properties ticketId(a unique identifier) and issueDateTime(the time the ticket is issued), a priority, and a description. The priority property is limited to the values low, medium, high, and emergency based on an enumeration. Listing 14–2 shows the XML schema, which defines the request and response for the web service ticket-issuing endpoint.

Listing 14–2. XML Schema Ticket.xsd

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<xs:schema version="1.0"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           targetNamespace="http://prospringintegration.com/tk/schemas"
           xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="TicketRequest">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="xs:string" name="description"/>
        <xs:element type="priorityType" name="priority"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:element name="TicketResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element type="ticketType" name="ticket"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

  <xs:complexType name="ticketType">
    <xs:sequence>
      <xs:element name="description" type="xs:string" minOccurs="0"/>
      <xs:element name="issueDateTime" type="xs:dateTime" minOccurs="0"/>
      <xs:element name="priority" type="priorityType" minOccurs="0"/>
      <xs:element name="ticketId" type="xs:long"/>
    </xs:sequence>
  </xs:complexType>

  <xs:simpleType name="priorityType">
    <xs:restriction base="xs:string">
      <xs:enumeration value="low"/>
      <xs:enumeration value="medium"/>
      <xs:enumeration value="high"/>
      <xs:enumeration value="emergency"/>
    </xs:restriction>
  </xs:simpleType>

</xs:schema>

Configuring a Web Services Inbound Gateway

There are two Spring Integration implementations for an inbound web services gateway: org.springframework.integration.ws.SimpleWebServiceInboundGateway and org.springframework.integration.ws.MarshallingWebServiceInboundGateway. The SimpleWebServiceInboundGateway will extract a javax.xml.tranform.Source from the org.springframework.ws.WebServiceMessage and set it as the message payload. The MarshallingServiceInboundGateway provides support for defining an implementation of the org.springframework.oxm.Marshaller and org.springframework.oxm.Unmarshaller interfaces, and will be discussed in more detail following. The SOAP action header will be added to the headers of the messages that are forwarded to the request channel for both gateways.

Both gateway implementations implement the Spring Web Services org.springframework.ws.server.endpoint.MessageEndpoint interface, so they can be configured using an org.springframework.ws.transport.http.MessageDispatcherServlet, similar to a standard Spring Web Services configuration. As with a Spring Web Services, the inbound web services gateway must be deployed within a servlet container with the standard directory structure and configuration files. The web.xml file is shown in Listing 14–3. The servlet class is set to the MessageDispacherServlet, and all requests with the URL pattern /ticketservice/* will be directed to the servlet.

Listing 14–3. Inbound Web Services Gateway web.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>Pro Spring Integration Inbound SOAP Gateway Example</display-name>

  <servlet>
    <servlet-name>ticket-ws</servlet-name>
    <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-images
class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>ticket-ws</servlet-name>
    <url-pattern>/ticketservice/*</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
  </welcome-file-list>

</web-app>

By convention, the Spring configuration file should have the name <servlet name>-servlet.xml and be placed in the webapp/WEB-INF directory. In our example, the name will be ticket-ws-servlet.xml, which is shown in Listing 14–4. The component-scanning element is used to create the references to the Java configuration and component beans, which will be discussed following.

Listing 14–4. Base Spring Configuration for Applying a Web Service Inbound Gateway service.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:int="http://www.springframework.org/schema/integration"
       xmlns:ws="http://www.springframework.org/schema/integration/ws"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/integration/ws
    http://www.springframework.org/schema/integration/ws/spring-integration-ws-2.0.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration-2.0.xsd">

  <context:component-scan
      base-package="com.apress.prospringintegration.webservice.web"/>

  <int:channel id="inboundDOMTicketRequest"/>

  <ws:inbound-gateway id="wsInboundGateway"
                      request-channel="inboundDOMTicketRequest"/>

  <int:service-activator input-channel="inboundDOMTicketRequest"
                         ref="ticketIssuerEndpoint"/>

</beans>

We will leverage the Spring Integration ws namespace to create the inbound web services gateway. The inbound-gateway element is configured to create a web services endpoint that forwards the web service invocation Source object containing the XML message to the inboundDomTicketRequest message channel defined by the request-channel attribute. The beauty of this example is that the underlying Spring Web Services libraries handle the SOAP protocol. Another option for the inbound-gateway is the reply-channel. By default, an anonymous channel is created and set through the REPLY_CHANNEL message header. Also, the extract-payload attribute can be set to false (it defaults to true) to pass the entire WebServicesMessage as the message payload sent to the request channel. Finally, the marshaller and unmarshaller can be set (this will be discussed in the next example).

A ticketIssuerEndpoint service activator is configured to receive the incoming XML message and to create a reply XML message. This service activator will be discussed following.

Configuring Web Services Endpoints

The web services endpoint is configured to send all incoming SOAP request invocations that match the URL pattern set in the web.xml file to the Spring Integration inbound gateway using the Java configuration file shown in Listing 14–5. An org.springframework.ws.server.endpoint.mapping.UriEndpointMapping instance is created, and the default endpoint is set to the Spring Integration gateway wsInboundGateway.

Listing 14–5. JavaConfiguration for the Web Services Endpoint

package com.apress.prospringintegration.webservice.web;

import com.apress.prospringintegration.webservice.CommonConfiguration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ws.server.endpoint.MessageEndpoint;
import org.springframework.ws.server.endpoint.mapping.UriEndpointMapping;

@Configuration
public class TicketIssuerConfiguration extends CommonConfiguration {

    //the ws:inbound-gateway is in fact a reference to this base Spring WS object
    @Value("#{wsInboundGateway}")
    private MessageEndpoint wsInboundGateway;

    @Bean
    public UriEndpointMapping uriEndpointMapping() {
        UriEndpointMapping uriEndpointMapping = new UriEndpointMapping();
        uriEndpointMapping.setDefaultEndpoint(wsInboundGateway);
        return uriEndpointMapping;
    }

}

Payload Extraction with DOM

Spring Web Services uses the org .springframework.ws.soap.saaj.SaajSoapMessageFactory to extract the XML payload from the incoming SOAP request. The request payload must be parsed with an XML parser. As shown in the service activator code in Listing 14–6, the XML payload is parsed using the Java default DOM parser. The description and priority are extracted from the incoming XML request and used to create the reply XML message. In this example, the XML reply payload is created by hand. A parser can be used to create the XML reply; however, this code is presented in contrast to the simplicity of using a marshaller, which will be demonstrated in the next example.

Listing 14–6. Service Activator Handling an Incoming Web Services Request

package com.apress.prospringintegration.webservice.web;

import com.apress.prospringintegration.webservice.CommonConfiguration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.xml.source.DomSourceFactory;
import org.springframework.stereotype.Component;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import java.util.Date;
import java.util.Random;

@Component
public class TicketIssuerEndpoint {

    private String replyTemplate =
            "<TicketResponse xmlns="" + CommonConfiguration.NAMESPACE + "">" +
                    "<ticket>" +
                    "<description>%s</description>" +
                    "<priority>%s</priority>" +
                    "<ticketId>%d</ticketId>" +
                    "<issueDateTime>%tc</issueDateTime>" +
                    "</ticket>" +
                    "</TicketResponse>";

    @ServiceActivator
    public Source handleRequest(DOMSource source)
            throws Exception {

        NodeList nodeList = source.getNode().getChildNodes();
        String description = "";
        String priority = "";

        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (node.getNodeName().equals("priority")) {
                priority = node.getFirstChild().getNodeValue();
            } else if (node.getNodeName().equals("description")) {
                description = node.getFirstChild().getNodeValue();
            }
        }

        // transfer properties to an XML document
        String xml = String.format(replyTemplate, description, priority,
                new Random().nextLong() * 1000, new Date());

        return new DomSourceFactory().createSource(xml);
    }
}

This Spring Integration example can be built and then deployed to a servlet container such as Tomcat or Jetty. To build thewar, simply execute mvn install, and copy the resulting war file within the target directory to the servlet container’s webapps directory.

Invoking Web Services Using an Outbound Gateway

Spring Integration has two outbound web services gateway implementations: org.springframework.integration.ws.SimpleWebServiceOutboundGateway and org.springframework.integration.ws.MarshallingWebServiceOutboundGateway. SimpleWebServiceOutboundGateway takes either a String or a Source an inbound message payload. MarshallingWebServiceOutboundGateway provides support for any implementation of the Marshaller and Unmarshaller interfaces. Both gateways require a Spring Web Services org.springframework.ws.client.support.destination.DestinationProvider for determining the URI of the web service to be invoked. Luckily, this will be handled behind the scenes using the Spring Integration ws namespace.

The Spring configuration file for the outbound web services gateway is shown in Listing 14–7. Note that a property-placeholder element is used to leverage the client.properties properties file shown in Listing 14–8. The gateway is configured using the outbound-gateway element. The gateway will respond to the message channel ticketRequest, which is set through the request-channel attribute. The outbound gateway requires that the URL for the web service endpoint be specified using the url attribute. In this example, the reply-channel attribute is not set. If the web service were to return a nonempty response, it would be returned to the request message’s REPLY_CHANNEL header. A specific reply channel can be set using the reply-channel attribute.

Listing 14–7. Spring Configuration for Outbound Gateway client.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ws="http://www.springframework.org/schema/integration/ws"
       xmlns:int="http://www.springframework.org/schema/integration"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
    http://www.springframework.org/schema/integration/ws
    http://www.springframework.org/schema/integration/ws/spring-integration-ws-2.0.xsd">

  <context:property-placeholder location="client.properties"/>

  <int:channel id="ticketRequests"/>

  <ws:outbound-gateway id="ticketIssueGateway" request-channel="ticketRequests"
                       uri="http://${ws.host}:${ws.port}/${ws.context}/ticketservice/images
tickets"/>


</beans>

Listing 14–8. Client Properties File client.properties

ws.host=127.0.0.1
ws.port=8080
ws.context=ticket-service-gateway-1.0

To construct an XML request message, either create a DOMSource instance or simply create a string representation of the XML structure. This example uses the latter approach, since is requires less code, and the XML request payload is rather short. The XML document is created to conform to the previous Ticket.xsd schema. The XML message is sent as a message payload to the ticketRequest message channel. The response message payload is logged to the console (Listing 14–9).

Listing 14–9. Client for Invoking Standard Web Services Endpoints

package com.apress.prospringintegration.webservice.client;

import com.apress.prospringintegration.webservice.CommonConfiguration;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.support.MessageBuilder;

public class TicketWebServiceDomClient {
    private static String bodyTemplate =
            "<TicketRequest xmlns="" + CommonConfiguration.NAMESPACE + "">" +
                    "<description>%s</description>" +
                    "<priority>%s</priority>" +
                    "</TicketRequest>";

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("client.xml");

        MessageChannel channel = context.getBean("ticketRequests", MessageChannel.class);

        String body = String.format(bodyTemplate, "Message Broker Down", "emergency");
        System.out.println(body);
        MessagingTemplate messagingTemplate = new MessagingTemplate();
        Message<?> message = messagingTemplate.sendAndReceive(
                channel, MessageBuilder.withPayload(body).build());

        System.out.println(message.getPayload());

    }

}

The results of running the web services gateway example are shown in Listing 14–10. The request XML message is shown, followed by the response message. Note the addition of the ticket ID and issue date time.

Listing 14–10. Results of Running the Web Services Gateway Example

<TicketRequest xmlns="http://prospringintegration.com/tk/schemas"><description>Message images
Broker Down</description><priority>emergency</priority></TicketRequest>
<?xml version="1.0" encoding="UTF-8"?><TicketResponse images
xmlns="http://prospringintegration.com/tk/schemas"><ticket><description>Message Broker images
Down</description><priority>emergency</priority><ticketId>-images
7242698475708198496</ticketId><issueDateTime>Sun Feb 27 00:21:30 PST images
2011</issueDateTime></ticket></TicketResponse>

Web Services and XML Marshalling

Spring Integration supports supplying a marshaller and unmarshaller for the inbound and outbound web services gateways. This allows using domain objects instead of working directly with the XML documents. This technology is also known as object/XML mapping (OXM). The marshalling library maps the domain object properties to the XML elements.

To implement gateway endpoints using XML marshalling, specify the marshaller and unmarshaller attributes on the inbound-gateway and outbound-gateway. Spring supports a variety of marshallers that leverage different XML-marshalling APIs, as shown in Table 14–1.

Table 14–1. Marshallers for Different XML-Marshalling APIs

API Marshaller
JAXB 1.0 org.springframework.oxm.jaxb.Jaxb1Marshaller
JAXB 2.0 org.springframework.oxm.jaxb.Jaxb2Marshaller
Castor org.springframework.oxm.castor.CastorMarshaller
XMLBeans org.springframework.oxm.xmlbeans.XmlBeansMarshaller
JiBX org.springframework.oxm.jibx.JibxMarshaller
XStream org.springframework.oxm.xstream.XStreamMarshaller

In this example, we will use Castor (www.castor.org) as the marshaller. Using other XML marshalling APIs with Spring is very similar. Castor supports working from existing data models or generating the domain objects from the XML schema. For very simple cases, the mappings can be written by hand. However, since the web service contract is typically fixed by the XML schema, this will be the starting point for this example. First, the Castor Maven dependency must be added to the pom.xml file, as shown in Listing 14–11.

Listing 14–11. Castor Maven Dependency

    <dependency>
      <groupId>org.codehaus.castor</groupId>
      <artifactId>castor-xml</artifactId>
      <version>1.3.1</version>
    </dependency>

Castor provides a Maven plug-in that will generate the domain classes to support a marshaller between the object and XML representation. The Maven plug-in configuration is shown in Listing 14–12. The location of the XML schema and the target package name are configured for the plug-in.

Listing 14–12. Castor Source Generation Plug-In pom.xml

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>castor-maven-plugin</artifactId>
        <version>2.0</version>
        <configuration>
          <schema>src/main/resources/Ticket.xsd</schema>
          <packaging>com.apress.prospringintegration.webservice.domain</packaging>
          <marshal>false</marshal>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

Castor has a number of options for specifying the mappings between the domain objects and the XML document. In this example, we will be using the descriptor class to define the mappings. The generated domain classes for the TicketRequest and TicketResponse are shown in Listings 14–13 and 14–14, respectively.

Listing 14–13. TicketRequest Domain Class Created by Castor (Comments Removed for Brevity)

package com.apress.prospringintegration.webservice.domain;

@SuppressWarnings("serial")
public class TicketRequest implements java.io.Serializable {
    private java.lang.String _description;
    private com.apress.prospringintegration.webservice.domain.types.PriorityType _priority;

    public TicketRequest() {
        super();
    }

    public java.lang.String getDescription(
    ) {
        return this._description;
    }

    public com.apress.prospringintegration.webservice.domain.types.PriorityType getPriority(
    ) {
        return this._priority;
    }

    public void setDescription(
            final java.lang.String description) {
        this._description = description;
    }

    public void setPriority(
            final com.apress.prospringintegration.webservice.domain.types.PriorityType images
priority) {
        this._priority = priority;
    }

}

Listing 14–14. TicketResponse Domain Class Created by Castor (Comments Removed for Brevity)

package com.apress.prospringintegration.webservice.domain;

@SuppressWarnings("serial")
public class TicketResponse implements java.io.Serializable {

    private com.apress.prospringintegration.webservice.domain.Ticket _ticket;

    public TicketResponse() {
        super();
    }

    public com.apress.prospringintegration.webservice.domain.Ticket getTicket(
    ) {
        return this._ticket;
    }

    public void setTicket(
            final com.apress.prospringintegration.webservice.domain.Ticket ticket) {
        this._ticket = ticket;
    }

}

An example of a generated Castor descriptor class for the TicketResponse domain class is shown in Listing 14–15.

Listing 14–15. Example of a Castor Descriptor Class (Some Comments Removed for Brevity)

package com.apress.prospringintegration.webservice.domain.descriptors;

import com.apress.prospringintegration.webservice.domain.TicketResponse;

public class TicketResponseDescriptor extends org.exolab.castor.xml.utilimages
.XMLClassDescriptorImpl {

    private boolean _elementDefinition;
    private java.lang.String _nsPrefix;
    private java.lang.String _nsURI;
    private java.lang.String _xmlName;
    private org.exolab.castor.xml.XMLFieldDescriptor _identity;

    public TicketResponseDescriptor() {
        super();
        _nsURI = "http://prospringintegration.com/tk/schemas";
        _xmlName = "TicketResponse";
        _elementDefinition = true;

        //-- set grouping compositor
        setCompositorAsSequence();
        org.exolab.castor.xml.util.XMLFieldDescriptorImpl desc = null;
        org.exolab.castor.mapping.FieldHandler handler = null;
        org.exolab.castor.xml.FieldValidator fieldValidator = null;
        //-- initialize attribute descriptors

        //-- initialize element descriptors

        //-- _ticket
        desc = new org.exolab.castor.xml.util.XMLFieldDescriptorImpl(
                com.apress.prospringintegration.webservice.domain.Ticket.class, "_ticket", images
"ticket",
                org.exolab.castor.xml.NodeType.Element);
        handler = new org.exolab.castor.xml.XMLFieldHandler() {
            @Override
            public java.lang.Object getValue(java.lang.Object object)
                    throws IllegalStateException {
                TicketResponse target = (TicketResponse) object;
                return target.getTicket();
            }

            @Override
            public void setValue(java.lang.Object object, java.lang.Object value)
                    throws IllegalStateException, IllegalArgumentException {
                try {
                    TicketResponse target = (TicketResponse) object;
                    target.setTicket((
                        com.apress.prospringintegration.webservice.domain.Ticket) value);
                } catch (java.lang.Exception ex) {
                    throw new IllegalStateException(ex.toString());
                }
            }

            @Override
            @SuppressWarnings("unused")
            public java.lang.Object newInstance(java.lang.Object parent) {
                return new com.apress.prospringintegration.webservice.domain.Ticket();
            }
        };
        desc.setSchemaType("com.apress.prospringintegration.webservice.domain.Ticket");
        desc.setHandler(handler);
        desc.setNameSpaceURI("http://prospringintegration.com/tk/schemas");
        desc.setRequired(true);
        desc.setMultivalued(false);
        addFieldDescriptor(desc);
        addSequenceElement(desc);

        //-- validation code for: _ticket
        fieldValidator = new org.exolab.castor.xml.FieldValidator();
        fieldValidator.setMinOccurs(1);
        { //-- local scope
        }
        desc.setValidator(fieldValidator);
    }

    @Override()
    public org.exolab.castor.mapping.AccessMode getAccessMode(
    ) {
        return null;
    }

    @Override()
    public org.exolab.castor.mapping.FieldDescriptor getIdentity(
    ) {
        return _identity;
    }

    @Override()
    public java.lang.Class getJavaClass(
    ) {
        return com.apress.prospringintegration.webservice.domain.TicketResponse.class;
    }

    @Override()
    public java.lang.String getNameSpacePrefix(
    ) {
        return _nsPrefix;
    }

    @Override()
    public java.lang.String getNameSpaceURI(
    ) {
        return _nsURI;
    }

    @Override()
    public org.exolab.castor.xml.TypeValidator getValidator(
    ) {
        return this;
    }

    @Override()
    public java.lang.String getXMLName(
    ) {
        return _xmlName;
    }

    public boolean isElementDefinition(
    ) {
        return _elementDefinition;
    }

}

With the domain objects and mappings created, the Spring Integration gateway can be configured to use Castor as the marshaller. The Spring configuration for the gateway example using Castor as the marshalling library is shown in Listing 14–16. This configuration is identical to the previous example, with the exception that a reference is added to the Castor marshaller using the inbound-gateway attributes mashaller and unmarshaller.

Listing 14–16. Spring Configuration File for the Web Services Example Using Marshalling ticket-ws-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:int="http://www.springframework.org/schema/integration"
       xmlns:ws="http://www.springframework.org/schema/integration/ws"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/integration/ws
    http://www.springframework.org/schema/integration/ws/spring-integration-ws-2.0.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration-2.0.xsd">

  <context:component-scan
      base-package="com.apress.prospringintegration.webservice.web"/>
  <context:component-scan
      base-package="com.apress.prospringintegration.webservice.service"/>

  <int:channel id="inboundOXMTicketRequest"/>

  <ws:inbound-gateway id="wsInboundGateway"
                      request-channel="inboundOXMTicketRequest"
                      marshaller="castorMarshaller"
                      unmarshaller="castorMarshaller"/>

  <int:service-activator input-channel="inboundOXMTicketRequest"
                         ref="ticketIssuerMarshallingEndpoint"/>

</beans>

The Castor marshaller will be used to handle the marshalling and unmarshalling processes. The org.springframework.oxm.castor.CastorMarshaller reference is created using Java configuration, as shown in Listing 14–17. The key property for the inbound gateway is setting the targetClass property with the base domain object that needs to be parsed TicketRequest. Once Castor knows the base object, it will find the child domain objects and descriptor files needed for parsing and mapping the XML request.

Listing 14–17. JavaConfiguration for Creating a Castor Marshaller Instance

package com.apress.prospringintegration.webservice.web;

import com.apress.prospringintegration.webservice.domain.TicketRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.oxm.castor.CastorMarshaller;

@Configuration
public class CastorConfiguration {
    @Bean
    public CastorMarshaller castorMarshaller() {
        CastorMarshaller castorMarshaller = new CastorMarshaller();
        castorMarshaller.setTargetClass(TicketRequest.class);
        return castorMarshaller;
    }
}

The inbound gateway uses the Castor marshaller to obtain the TicketRequest instance and forwards the instance on as a message payload to the inboundOXMTicketRequest channel. The message is then picked up by the service activator shown in Listing 14–18. Note that the incoming message to the service activator is the TicketRequest instance. Because of the Castor marshaller, there is no need to parse any XML document.

Listing 14–18. Gateway Service Activator That Issues Tickets

package com.apress.prospringintegration.webservice.web;

import com.apress.prospringintegration.webservice.domain.Ticket;
import com.apress.prospringintegration.webservice.domain.TicketRequest;
import com.apress.prospringintegration.webservice.domain.TicketResponse;
import com.apress.prospringintegration.webservice.service.TicketIssuerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.stereotype.Component;

@Component
public class TicketIssuerMarshallingEndpoint {

    @Autowired
    private TicketIssuerService ticketIssuerService;

    @ServiceActivator
    public TicketResponse handleRequest(TicketRequest tr) throws Exception {
        System.out.println("TicketRequest: " + tr);
        TicketResponse ticketResponse = new TicketResponse();
        Ticket t = ticketIssuerService.issueTicket(tr.getDescription(),
                tr.getPriority().name());
        ticketResponse.setTicket(t);
        return ticketResponse;
    }
}

The ticketIssuerService component shown in Listing 14–19 is used to generate the ticket with a unique ID and issue data time. The service activator then returns a TicketResponse object back to the gateway, and it is forwarded back to the client.

Listing 14–19. Ticket Issuer Service Component

package com.apress.prospringintegration.webservice.service;

import com.apress.prospringintegration.webservice.domain.Ticket;
import com.apress.prospringintegration.webservice.domain.types.PriorityType;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Random;

@Component
public class TicketIssuerService {
    public Ticket issueTicket(String description, String priority) throws Exception {
        Ticket ticket = new Ticket();
        ticket.setDescription(description);
        ticket.setPriority(PriorityType.valueOf(priority));
        ticket.setTicketId(new Random().nextLong() * 1000);
        ticket.setIssueDateTime(new Date());

        return ticket;
    }
}

WSDL Options

A WSDL is a service contract between the web services provider (server) and service requestor (client), and is primarily used for SOAP services. It is similar to the contract created by an interface for a Java class. Spring Web Services provides support for exposing a WSDL using an instance of org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition. DefaultWsdl11Definition, which autogenerates the WSDL based an instance of org.springframework.xml.xsd.SimpleXsdSchema. In our example, SimpleXsdSchema is used to expose Ticket.xsd as source schema for WSDL generation, as shown in the Java configuration in Listing 14–20. org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping is used to map the incoming request to the service endpoints. In addition, org.springframework.web.servlet.handler.SimpleUrlHandlerMapping is used to specifically expose the WSDL file through the URI /tickets.wsdl.

Listing 14–20. Generating a WSDL File Using an XML Schema

package com.apress.prospringintegration.webservice.web;

import com.apress.prospringintegration.webservice.domain.TicketRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.castor.CastorMarshaller;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.ws.server.EndpointInterceptor;
import org.springframework.ws.server.endpoint.MessageEndpoint;
import org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor;
import org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping;
import org.springframework.ws.soap.server.SoapMessageDispatcher;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@SuppressWarnings("unused")
@Configuration
public class TicketIssuerServiceConfiguration {
    public static String NAMESPACE = "http://prospringintegration.com/tk/schemas";

    // the ws:inbound-gateway is in fact a reference to this base Spring WS object
    @Value("#{wsInboundGateway}")
    private MessageEndpoint wsInboundGateway;

    @Bean
    public XsdSchema schema() {
        SimpleXsdSchema xsdSchema = new SimpleXsdSchema();
        xsdSchema.setXsd(new ClassPathResource("Ticket.xsd"));
        return xsdSchema;
    }

    @Bean
    public DefaultWsdl11Definition tickets() throws Throwable {
        DefaultWsdl11Definition defaultWsdl11Definition =
                new DefaultWsdl11Definition();
        defaultWsdl11Definition.setSchema(schema());
        defaultWsdl11Definition.setPortTypeName("TicketRequest");
        defaultWsdl11Definition.setLocationUri("/tickets");
        defaultWsdl11Definition.setTargetNamespace(NAMESPACE);

        return defaultWsdl11Definition;
    }

    @Bean
    public PayloadRootQNameEndpointMapping payloadRootQNameEndpointMapping() {
        String fqn = String.format("{%s}%s", NAMESPACE, "TicketRequest");
        Map<String, MessageEndpoint> endpoints = new HashMap<String, MessageEndpoint>();
        endpoints.put(fqn, wsInboundGateway);
        PayloadRootQNameEndpointMapping payloadRootQNameEndpointMapping =
                new PayloadRootQNameEndpointMapping();
        payloadRootQNameEndpointMapping.setEndpointMap(endpoints);
        payloadRootQNameEndpointMapping.setInterceptors(
                new EndpointInterceptor[]{new PayloadLoggingInterceptor()});
        return payloadRootQNameEndpointMapping;
    }

    @Bean
    public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
        SimpleUrlHandlerMapping simpleHandlerMapping = new SimpleUrlHandlerMapping();
        simpleHandlerMapping.setDefaultHandler(soapMessageDispatcher());
        Properties urlMap = new Properties();
        urlMap.setProperty("*.wsdl", "tickets");
        simpleHandlerMapping.setMappings(urlMap);
        return simpleHandlerMapping;
    }

    @Bean
    public SoapMessageDispatcher soapMessageDispatcher() {
        return new SoapMessageDispatcher();
    }
}

To test the inbound web services gateway example using Castor marshalling, the ticket-service-marshaller project must be built and deployed to a servlet container such as Tomcat or Jetty. To build thewar, simply execute mvn install and copy the resulting war file within the target directory to the servlet container’s webapps directory. Use a browser and go to the URL http://localhost:8080/ticket-service-marshalling-1.0/ticketservice/tickets.wsdl. The WSDL shown in Listing 14–21 will appear, describing the inbound gateway endpoint. You can also use this WSDL to create a client using one of the WSDL-to-Java tools available from projects such as Axis, CXF, orXFire.

Listing 14–21. Ouput of WSDL Generation tickets.wsdl

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:sch="http://prospringintegration.com/tk/schemas"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:tns="http://prospringintegration.com/tk/schemas"
                  targetNamespace="http://prospringintegration.com/tk/schemas">
  <wsdl:types>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
               attributeFormDefault="unqualified"
               elementFormDefault="qualified"
               targetNamespace="http://prospringintegration.com/tk/schemas"
               version="1.0">

      <xs:element name="TicketRequest">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="description" type="xs:string"/>
            <xs:element name="priority" type="priorityType"/>
          </xs:sequence>

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

      <xs:element name="TicketResponse">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="ticket" type="ticketType"/>
          </xs:sequence>
        </xs:complexType>

      </xs:element>

      <xs:complexType name="ticketType">
        <xs:sequence>
          <xs:element minOccurs="0" name="description" type="xs:string"/>
          <xs:element minOccurs="0" name="issueDateTime" type="xs:dateTime"/>
          <xs:element minOccurs="0" name="priority" type="priorityType"/>
          <xs:element name="ticketId" type="xs:long"/>
        </xs:sequence>

      </xs:complexType>

      <xs:simpleType name="priorityType">
        <xs:restriction base="xs:string">
          <xs:enumeration value="low"/>
          <xs:enumeration value="medium"/>
          <xs:enumeration value="high"/>
          <xs:enumeration value="emergency"/>
        </xs:restriction>

      </xs:simpleType>

    </xs:schema>
  </wsdl:types>
  <wsdl:message name="TicketRequest">
    <wsdl:part element="tns:TicketRequest" name="TicketRequest">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="TicketResponse">
    <wsdl:part element="tns:TicketResponse" name="TicketResponse">

    </wsdl:part>
  </wsdl:message>
  <wsdl:portType name="TicketRequest">
    <wsdl:operation name="Ticket">
      <wsdl:input message="tns:TicketRequest" name="TicketRequest">
      </wsdl:input>
      <wsdl:output message="tns:TicketResponse" name="TicketResponse">
      </wsdl:output>
    </wsdl:operation>

  </wsdl:portType>
  <wsdl:binding name="TicketRequestSoap11" type="tns:TicketRequest">
    <soap:binding style="document"
                  transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="Ticket">
      <soap:operation soapAction=""/>
      <wsdl:input name="TicketRequest">
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output name="TicketResponse">

        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="TicketRequestService">
    <wsdl:port binding="tns:TicketRequestSoap11" name="TicketRequestSoap11">
      <soap:address
          location="http://localhost:8080/ticket-service-marshalling-1.0/tickets"/>
    </wsdl:port>
  </wsdl:service>

</wsdl:definitions>

Outbound Web Services Gateway with XML Marshalling

The Castor marshaller can also be used with the outbound web services gateway. This example will use the same domain and descriptor mapping classes from the inbound gateway example. Similar to the inbound-gateway configuration, the Castor marshaller is set through the marshaller and unmarshaller attributes of the outbound-gateway element, as shown in Listing 14–22.

Listing 14–22. Spring Configuration for Gateway Client Using the Castor Marshaller client.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:ws="http://www.springframework.org/schema/integration/ws"
       xmlns:int="http://www.springframework.org/schema/integration"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/integration
    http://www.springframework.org/schema/integration/spring-integration-2.0.xsd
    http://www.springframework.org/schema/integration/ws
    http://www.springframework.org/schema/integration/ws/spring-integration-ws-2.0.xsd">

  <context:component-scan base-package="com.apress.prospringintegration.webservice.client"/>
  <context:property-placeholder location="client.properties"/>

  <int:channel id="ticketRequests"/>

  <ws:outbound-gateway id="ticketIssueGateway"
            request-channel="ticketRequests"
            uri="http://${ws.host}:${ws.port}/${ws.context}/ticketservice/tickets"
            marshaller="castorMarshaller" unmarshaller="castorMarshaller"/>
</beans>

The Java configuration for the Castor marshaller is shown in Listing 14–23. Since the TicketRequest instance is sent first to the marshaller, Castor is able to get the reference to this instance and the related domain and descriptor mapping classes.

Listing 14–23. JavaConfiguration for the Outbound Gateway Client Using the Castor Marshaller

package com.apress.prospringintegration.webservice.client;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.oxm.castor.CastorMarshaller;

@Configuration
public class TicketWebServiceMarshallingConfiguration {
    @Bean
    public CastorMarshaller castorMarshaller() {
        CastorMarshaller castorMarshaller = new CastorMarshaller();
        return castorMarshaller;
    }
}

The client code is similar to the first outbound gateway example, as shown in Listing 14–24. However, in this case XML document isn’t created by hand; instead, all the interaction with the gateway occurs through the domain objects.

Listing 14–24. Outbound Gateway Using the Castor Marshaller Client Class

package com.apress.prospringintegration.webservice.client;

import com.apress.prospringintegration.webservice.domain.Ticket;
import com.apress.prospringintegration.webservice.domain.TicketRequest;
import com.apress.prospringintegration.webservice.domain.TicketResponse;
import com.apress.prospringintegration.webservice.domain.types.PriorityType;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Component;

@Component
public class TicketWebServiceMarshallingClient {

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("client.xml");

        MessageChannel channel =
                context.getBean("ticketRequests", MessageChannel.class);

        MessagingTemplate messagingTemplate = new MessagingTemplate();

        TicketRequest tr = new TicketRequest();
        tr.setDescription("Message Broker Down");
        tr.setPriority(PriorityType.EMERGENCY);
        System.out.printf("Ticket Request: %s [priority: %s] %n", tr.getDescription(),
                tr.getPriority());
        Message<TicketRequest> ticketRequestMessage =
                MessageBuilder.withPayload(tr).build();

        @SuppressWarnings("unchecked")
        Message<TicketResponse> message =
                (Message<TicketResponse>) messagingTemplate.sendAndReceive(
                        channel, ticketRequestMessage);

        Ticket ticket = message.getPayload().getTicket();
        System.out.printf("Ticket Response: %s [id: %d] [priority: %s] [date: %s]%n",
                ticket.getDescription(), ticket.getTicketId(),
                ticket.getPriority(), ticket.getIssueDateTime());

    }
}

The results of running the outbound gateway client code are shown in Listing 14–25. The TicketRequest object is created and sent to the outbound gateway. Castor marshalling handles parsing the XML. The gateway invokes the web service endpoint and returns the TicketResponse object with the new ticket issued.

Listing 14–25. Web Services Gateway Example Results Using the Castor Marshaller

Ticket Request: Message Broker Down [priority: emergency]
Ticket Response: Message Broker Down [id: 5433537107850368600] [priority: emergency] [date:images
Sun Feb 27 12:18:01 PST 2011]

Summary

Spring Integration provides two components for supporting web services: invoking a web service by sending a message to a channel using an outbound web service gateway and sending a message to a channel upon receiving a web service invocation using an inbound web service gateway. Both the inbound and outbound gateways include support for adding an XML marshaller. We have covered how Spring Integration provides both client and server support for web services and how to integrate web services with the Spring Integration messaging framework. We used the Castor project as an example of providing a marshaller for the Spring Integration gateways, and we explored how to use the Spring Web Services project to expose a WSDL providing a full description of the web service endpoints.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.154.139