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.
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>
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>
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-
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.
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;
}
}
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.
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/
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
Broker Down</description><priority>emergency</priority></TicketRequest>
<?xml version="1.0" encoding="UTF-8"?><TicketResponse
xmlns="http://prospringintegration.com/tk/schemas"><ticket><description>Message Broker
Down</description><priority>emergency</priority><ticketId>-
7242698475708198496</ticketId><issueDateTime>Sun Feb 27 00:21:30 PST
2011</issueDateTime></ticket></TicketResponse>
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
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.util
.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",
"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;
}
}
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>
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:
Sun Feb 27 12:18:01 PST 2011]
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.
3.17.154.139