In this chapter, we will cover:
Using Maven for building and running a Spring-WS project
Creating a data contract
Setting up a Web-Service using DispatcherServlet
Simplifying the creation of a Web-Service using MessageDispatcherServlet
Setting up a Web-Service on JMS transport
Setting up a Web-Service on E-mail transport
Setting up a Web-Service on embedded HTTP server transport
Setting up a Web-Service on XMPP transport
Setting up a simple endpoint mapping for the Web-Service
Setting up a contract-first Web-Service
Setting up an endpoint by annotating the payload-root
Setting up a transport-neutral WS-Addressing endpoint
Setting up an endpoint using an XPath expression
Handling the incoming XML messages using DOM
Handling the incoming XML messages using JDOM
Handling the incoming XML messages using JAXB2
Validating the XML messages on the server side using an interceptor
SOAP (Simple Object Access Protocol) was designed to be language-, transport-, and platform-independent, which is an alternative to the old fashioned middleware technologies such as CORBA and DCOM. SOAP was also designed to be extensible. The standards referred to as WS-* — WS-Addressing, WS-Policy, WS-Security, and so on are built on the SOAP protocol.
The Web-Services that use SOAP, along with WSDL and XML schema, have become the standard for exchanging the XML-based messages. The Spring Web-Services facilitate SOAP service development, by providing a comprehensive set of APIs and configurations for the creation of flexible Web-Services. The following diagram shows how a Spring-WS works when it receives an incoming message (the diagram is in abstract form):
MessageDispatcher
is the central point for a Spring Web-Service and dispatches Web-Service messages to the registered endpoint. In Spring-WS, request/response messages are wrapped inside the MessageContext
object and the MessageContext
will be passed to the MessageDispatcher
(response will be set into MessageContext after invoking the endpoint). When a message arrives, MessageDispatcher uses the request object to get the endpoint. (Mapping a request to an endpoint is called endpoint mapping and it can be done by using data from beans registration within application context, scanning, and autodetection of annotations). Then the MessageDispatcher
by using the endpoint, gets endpopint's interceptors (which range from zero to many) and calls handleRequest method on them.
An interceptor (EndpointInterceptor
here), as the name suggests, intercepts the request/response to perform some operations prior to (for request)/after (for response) invoking the endpoint. This EndpointInterceptor
gets called before/after calling the appropriate endpoint to perform several processing aspects such as logging, validating, security, and so on. Next, MessageDispatcher
gets appropriate endpoint adapter for the endpoint method to be called. This adapter offers compatibility with various types of endpoint methods. Each adapter is specialized to call a method with specific method parameter and return type.
And Finally, EndpointAdapter
invokes the endpoint's method and transforms the response to the desired form and set it into the MessageContext
object. Now the initial message context that was passed to MessageDispatcher
, contains the response object, that will be forwarded to the client (by the caller of MessageDispatcher)
.
Spring-WS only supports the contract-first development style in which creating the contract (XSD or WSDL) is the first step. The required steps to build a contract-first Web-Service using Spring-WS are as follows:
Contract definition (either XSD or WSDL)
Creating endpoint: the class that receives and processes an incoming message.
Configuration of Spring beans and the endpoint.
There are two types of endpoints, namely, payload endpoints and message endpoints. While message endpoints can access the entire XML SOAP envelop, the payload endpoint will only access the payload part of a SOAP envelop, that is, the body of a SOAP envelop. In this book, the focus is on creating payload endpoints.
In this chapter, after a recipe for the explanation of creating contract from a set of XML messages, the major focus will be on implementing endpoints and its related configuration.
For the purpose of illustrating the construction process of Web-Services, this book uses a simple business scenario of a fictitious restaurant, Live Restaurant, which needs to accept online orders from customers. Live Restaurant decides to publish its OrderService
component as a Web-Service. For simplicity, just two operations are considered for the OrderService
(Java interface).
The project will follow the following domain model:
Each recipe in this book will incrementally build parts of the project to make it a complete Web-Service application. The Java project name is LiveRestaurant
, and each recipe will use a slightly different version of the project, with the extension _R-x.x
. For example, the first recipe in this chapter will use LiveRestaurant_R-1.1
for the Web-Service server and LiveRestaurant_R-1.1-Client for the client
as the project name.
Setting up a Web-Service is the goal of this chapter, so more emphasis is on explanation of the server-side code and settings. Client-side code is used in this chapter for checking the functionality of the server. More about client side code, settings, and testing will be discussed in the following chapters.
Recent modern software development, based on enterprise-grade open source technologies, requires a new generation of build and project management tools. Such tools can make a standard way for building, managing, and deploying small scale to large scale applications.
Maven, hosted by the Apache Software Foundation, is a project management and automated build and deploy tool. Maven is built upon Ant's features and adds several features such as feature dependency and project management. Maven was initially used for Java programming, but it can also be used to build and manage projects written in other programming languages. In recent years, Maven has been used to automate the process of building, managing, and testing the deployments of major open source projects.
This recipe details the steps required to set up Maven for building, testing, and deploying the projects used in this book.
This recipe requires the installation of the following software or tools:
Java 6 or higher and Maven 3.0.2: For download and installation, refer to http://maven.apache.org/ and http://www.oracle.com/technetwork/java/javase/downloads/index.html.
Add your custom repositories to settings.xml
under MAVEN_HOME/conf
or .m2
folders (MAVEN_HOME
is the folder in which Maven is installed and .m2
is the folder in which Maven downloads its artifacts to).
Later, you can add an extra repository to your custom repositories. You can disable this repository by setting activeByDefault
to false
(the file that contains repositories is in the resources
folder):
<profile> <id>my-repository</id> <activation> <activeByDefault>true</activeByDefault> </activation> <!-- list of standard repository --> <repositories> ... ... <repository> <id>maven2-repository.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/2</url> </repository> .... <repository> <id>maven1-repository.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/1</url> </repository> </repositories> </profile>
An alternative way to include the Maven repositories to your Maven build is to include repository data in the POM file directly. Samples of both ways to include repositories are included under the Using Maven
folder in the resource bundle of this chapter.
Build and deploy a project.
mvn clean package tomcat:run
Browse the following Web-Service WSDL file:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
The following is the browser's output:
<wsdl:definitions targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema"> <wsdl:types> <schema elementFormDefault="qualified" targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema"> <element name="placeOrderRequest"> <complexType> <sequence> <element name="order" type="QOrder:Order" /> </sequence> </complexType> ........ </schema> </wsdl:types> ....... <wsdl:binding name="OrderServiceSoap11" type="tns:OrderService"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="placeOrder"> <soap:operation soapAction="" /> <wsdl:input name="placeOrderRequest"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="placeOrderResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> <wsdl:operation name="cancelOrder"> <soap:operation soapAction="" /> <wsdl:input name="cancelOrderRequest"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="cancelOrderResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="OrderServiceService"> <wsdl:port binding="tns:OrderServiceSoap11" name="OrderServiceSoap11"> <soap:address location="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
The following is the output of the Maven command:
...........
[INFO] Building war: C:...LiveRestaurant.war
.......
[INFO] --- tomcat-maven-plugin:1.1:run ...@ LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration ...
Oct 15,...org.apache.catalina.startup.Embedded start
INFO: Starting tomcat server
Oct 15...org.apache.catalina.core.StandardEngine start
INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
org.apache.catalina.core.ApplicationContext log
...Set web app root ..: 'webapp.root' = [...srcmainwebapp]
INFO: Initializing log4j from..WEB-INFlog4j.properties]
...
INFO: Initializing Spring FrameworkServlet 'spring-ws'
......
INFO .. - FrameworkServlet 'spring-ws': initialization ..
Oct .. org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
Oct .. org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
In order to import a Maven project into an Eclipse IDE:
Go to the root of the project (chapterOneLiveRestaurant_R-1.1) and execute:
mvn eclipse:eclipse -Declipse.projectNameTemplate="LiveRestaurant_R-1.1"
Then, you can import the Maven project as an Eclipse project.
In case Maven cannot find a JAR file, you can use your custom repository using the following command:
mvn -P my-repository clean package tomcat:run
mvn clean package
installs the required components into a local repository and creates a WAR/JAR file of the project:
[INFO] Building war: ...LiveRestaurant.war
mvn tomcat:run
runs a WAR file of the project on the Tomcat plugin. mvn jetty:run
runs the WAR file of the project on the Jetty plugin:
INFO] --- tomcat-maven-plugin:1.1:... LiveRestaurant ---
[INFO] Running war on http://localhost:8080/LiveRestaurant
[INFO] Creating Tomcat server configuration at
A WSDL document, known as a service contract, provides a standard way in which a Web-Service client and server exchange data. Using WSDL, the client and server could be on a different application or platform. XML Schema Definition(XSD), known as data contract, describes the structure of the datatypes that are being exchanged between the Web-Service server and client. XSD describes the types, fields, and any validation on those fields (such as max/min or pattern, and so on). While WSDL is specific to the Web-Service and describes a Web-Service's artifacts, such as methods and data passed through these methods (WSDL itself uses an XSD for that), URL, and so on; XSD only presents the structure of the data.
To be able to set up a Spring Web-Service, we need a contract. There are four different ways of defining such a contract for XML:
DTDs
XML Schema (XSD)
RELAX NG
Schematron
DTDs have limited namespace support, so they are not suitable for Web-Services. RELAX NG and Schematron certainly are easier than XML Schema. Unfortunately, they are not so widely supported across platforms. Spring-WS uses XML Schema.
A data contract is the center of Spring-WS and a service contract can be generated from a data contract. The easiest way to create an XSD is to infer it from the sample documents. Any good XML editor or Java IDE offers this functionality. Basically, these tools use some sample XML documents and generate a schema from it that validates them all. In this recipe, we will discuss sample XML data massages and how to convert them into a single schema file. The generated schema is used in this book as a data contract.
Install xmlbeans-2.5.0 from http://xmlbeans.apache.org/.
The resources for this recipe are included in the folder Create Data Contract.
Copy your XML messages (placeOrderRequest.xml, placeOrderResponse, cancelOrderRequest.xml
, and cancelOrderResponse.xml)
to the xmlbeans-2.5.0in
working folder.
Run the following command:
inst2xsd -design rd -enumerations never placeOrderRequest.xml placeOrderResponse.xml cancelOrderRequest
The preceding command creates the schema0.xsd
schema file. The generated schema result certainly needs to be modified, but it's a great starting point. Here is the final polished schema (orderService.xsd)
:
<?xml version="1.0" encoding="UTF-8"?> ...... <schema..."> <element name="placeOrderRequest"> <complexType> <sequence> <element name="order" type="QOrder:Order"></element> </sequence> </complexType> </element> <element name="placeOrderResponse"> <complexType> <sequence> <element name="refNumber" type="string"></element> </sequence> </complexType> </element> ......... data contractdata contractcreating<complexType name="Order"> <sequence> <element name="refNumber" type="string"></element> <element name="customer" type="QOrder:Customer"></element> <element name="dateSubmitted" type="dateTime"></element> <element name="orderDate" type="dateTime"></element> <element name="items" type="QOrder:FoodItem" maxOccurs="unbounded" minOccurs="1"> </element> </sequence> </complexType> <complexType name="Customer"> <sequence> <element name="addressPrimary" type="QOrder:Address"></element> <element name="addressSecondary" type="QOrder:Address"></element> <element name="name" type="QOrder:Name"></element> </sequence> </complexType> .... </schema>
Initially, the input and output sample messages are required. In this book, there are four XML messages (placeOrderRequest.xml, placeOrderResponse, cancelOrderRequest.xml
, and cancelOrderResponse.xml)
and all the recipes use these message data formats for communication. Inst2xsd
generates a schema file from the existing XML sample messages. Resources of this recipe are included under the Create Data Contract
folder in the resource bundle of this chapter.
Spring-WS provides one of the easiest mechanisms to develop Web-Services in the Java platform. This recipe focuses on building a very simple Web-Service using the Spring-MVC DispatcherServlet
and the components provided by Spring-WS.
In this recipe, the project's name is LiveRestaurant_R-1.2
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
Copy the service contract from the resources
folder (orderService.wsdl).
Create an endpoint (OrderSeviceMessageReceiverEndpoint)
.
Configure the endpoint, service contract, WebServiceMessageReceiverHandlerAdapter, MessageDispatcher
, and WsdlDefinitionHandlerAdapter
, in the server Spring configuration file (Dispatcher-servlet.xml)
.
Configure DispatcherServlet
inside the web.xml
file.
Run the server using the following command:
mvn clean package tomcat:run
The following is the output:
...........................
[INFO] Running war on http://localhost:8080/LiveRestaurant
...................................
18-Oct-2011 10:23:02.....ApplicationContext log
INFO: Initializing Spring FrameworkServlet 'Dispatcher'
18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol init
INFO: Initializing Coyote HTTP/1.1 on http-8080
18-Oct-2011 10:23:02 org.apache.coyote.http11.Http11Protocol start
INFO: Starting Coyote HTTP/1.1 on http-8080
To browse your service WSDL, open the following link inside your browser:
http://localhost:8080/LiveRestaurant/Dispatcher/OrderService.wsdl
To test, open a new command window, go to the folder LiveRestaurant_R-1.2-Client
, and run the following command:
mvn clean package exec:java
The following is the server-side output:
Inside method, OrderSeviceMethodEndpoint.receive - message content = <?xml version="1.0" encoding="UTF-8"?><tns:placeOrderRequest xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
DispatcherServlet
receives all the incoming requests, and based on request context, it forwards the request to the endpoint (the general form of a request URL is http://<host>:<port>/<appcontext>/<requestcontext>
(here appcontext
is Liverestaurant and requestcontext
should start with /Dispatcher/)
. The requests context that ends with /OrderService
go to OrderSeviceMessageReceiverEndpoint
and requests that end with *.wsdl
go to SimpleWsdl11Definition)
.
DispatcherServlet
configured in web.xml
is responsible for receiving all requests with a URL mapping [/Dispatcher/*]
.
<servlet>
<servlet-name>Dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Dispatcher</servlet-name>
<url-pattern>/Dispatcher/*</url-pattern>
</servlet-mapping>
You can change the URL pattern to suit your requirement.
DispatcherServlet
plays a major role in intercepting the HTTP requests and then loads the Spring bean configuration file. By default, it detects the bean configuration file by name<servlet-name>-servlet.xml
. Since we have named the DispatcherServlet
as Dispatcher
in web.xml
file, the server looks for Dispatcher-servlet.xml
as application context filename. You may configure another file, using the following context param
in the web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/classes/applicationContext.xml</param-value>
</context-param>
DispatcherServlet
needs separate instances of WebServiceMessageReceiverHandlerAdapter, MessageDispatcher
, and WsdlDefinitionHandlerAdapter
that in this recipe are configured inside Dispatcher-servlet.xml
. The DispatcherServlet
, by default, delegates to controllers for handling requests, but in the configuration file, it is configured to delegate to a MessageDispatcher
(WebServiceMessageReceiverHandlerAdapter). SaajSoapMessageFactory
is a specific message factory for message creation in Spring-WS.
<beans ..."> <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"> <property name="messageFactory"> <bean class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"></bean> </property> </bean> .......
To let DispatcherServlet
handle the WSDL contract, WsdlDefinitionHandlerAdapter
, which is registered in the configuration file; it reads the WSDL file source using the WsdlDefinition
implementation (SimpleWsdl11Definition)
and writes that as the result to the HttpServletResponse
.
SimpleUrlHandlerMapping
is to redirect the client requests to the appropriate endpoints using the URL patterns. Here the request URL that ends with *.wsdl
will be redirected to sampleServiceDefinition
(that is, SimpleWsdl11Definition
that uses OrderService.wsdl
to generate the response), and if the request URL contains /OrderService
, it will be redirected to OrderSeviceMessageReceiverEndpoint. SOAPMessageDispatcher
is to dispatch a SOAP message to the registered endpoint(s) (OrderSeviceMessageReceiverEndpoint)
.
....... <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="*.wsdl">sampleServiceDefinition</prop> <prop key="/OrderService">OrderServiceEndpoint</prop> </props> </property> <property name="defaultHandler" ref="messageDispatcher"/> </bean> <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/> <bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMessageReceiverEndpoint"/> <bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/> <bean id="sampleServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition"> <property name="wsdl" value="/WEB-INF/OrderService.wsdl"/> </bean> </beans> OrderSeviceMessageReceiverEndpoint is a very basic endpoint that get incoming message (messageContext.getRequest().getPayloadSource()) and prin it out: ........ public class OrderSeviceMessageReceiverEndpoint implements WebServiceMessageReceiver { public OrderSeviceMessageReceiverEndpoint() { } public void receive(MessageContext messageContext) throws Exception { System.out .println("Inside method, OrderSeviceMethodEndpoint.receive - message content = " + xmlToString(messageContext.getRequest().getPayloadSource())); }
private String xmlToString(Source source) { try { StringWriter stringWriter = new StringWriter(); Result result = new StreamResult(stringWriter); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform(source, result); return stringWriter.getBuffer().toString(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } return null; } }
MessageDispatcherServlet
is the core component of Spring-WS. With simple configuration, a Web-Service can be set up in minutes. This servlet came as a simple way to configure an alternative to the Spring-MVC DispatcherServlet
. As in the second recipe, Setting up a Web-Service using DispatcherServlet, DispatcherServlet
needs separate instances of WebServiceMessageReceiverHandlerAdapter, MessageDispatcher
, and WsdlDefinitionHandlerAdapter
. However, MessageDispatcherServlet
can dynamically detect EndpointAdapters, EndpointMappings, EndpointExceptionResolvers
, and WsdlDefinition
by setting inside the application context.
Since this is the default method for configuring Spring Web-Services, it will be used in later recipes. In this recipe, a very basic implementation of setting up a Spring-WS is detailed. More advance implementation will be explained later in the recipe Setting up a contract-first Web-Service.
In this recipe, the project's name is LiveRestaurant_R-1.3
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
Copy the service contract from the resources
folder (orderService.wsdl)
.
Create an endpoint (OrderSeviceMethodEndpoint)
.
Configure the endpoint. The service contract is in the server Spring configuration file (spring-ws-servlet.xml)
.
Configure MessageDispatcherServlet
inside the web.xml
file.
Run the server using the following command:
mvn clean package tomcat:run
The following is the output after the server is run successfully:
...........................
[INFO] >>> tomcat-maven-plugin:1.1:run .. LiveRestaurant >>>
[..............
[INFO] Running war on http://localhost:8080/LiveRestaurant
[I...........
..XmlBeanDefinitionReader.. Loading..spring-ws-servlet.xml]
...
..SimpleMethodEndpointMapping#0, OrderService, OrderServiceEndpoint]; root of factory hierarchy
INFO [main] (SaajSoapMessageFactory.java:135) -..
INFO [main] (FrameworkServlet.java:320) - FrameworkServlet '
........
INFO: Starting Coyote HTTP/1.1 on http-8080
To browse your service WSDL, open the following link in your browser:
http://localhost:8080/LiveRestaurant/spring-ws/OrderService.wsdl
To test, open a new command window, go to the folder LiveRestaurant_R-1.3-Client
, and run the following command:
mvn clean package exec:java
The following is the server-side output:
Sent response
...
<tns:placeOrderResponse....>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>
.....
for request
<tns:placeOrderRequest.... >
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
....
The MessageDispatcherServlet
is configured in the web configuration file web.xml:
<servlet> <servlet-name>spring-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>spring-ws</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.
MessageDispatcherServlet
is the central element that handles the incoming SOAP requests, with the help of other components (EndpointAdapters, EndpointMappings, EndpointExceptionResolvers
, and WsdlDefinition)
. It combines the attributes of both DispatcherServlet
and MessageDispatcher
that dispatch to the appropriate endpoint. This is the standard servlet recommended to build Web-Services with Spring-WS.
Since the MessageDispatcherServlet
is inherited from FrameworkServlet
, it looks for a configuration file named<servlet-name>-servlet.xml
in the class path (you can change the configuration filename using the context-param, contextConfigLocation
settings in the web.xml
, as described in the recipe Setting up a Web-Service using DispatcherServlet). In the example, since the servlet name in the web.xml
file is set to Spring-WS, the file spring-ws-servlet.xml
is the Web-Services configuration file.
MessageDispatcherServlet
then looks up for an endpoint mapping element in the configuration file, for the purpose of mapping the client requests to the endpoint. Here,<sws:static-wsdl
sets the data contract in the WSDL format. This is the element to be configured in spring-ws-servlet.xml
to set up a Web-Service:
<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping"> <property name="endpoints"> <ref bean="OrderServiceEndpoint"/> </property> <property name="methodPrefix" value="handle"></property> </bean> <sws:static-wsdl id="OrderService" location="/WEB-INF/orderService.wsdl"/> <bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint"> </bean>
The example uses SimpleMethodEndpointMapping
that maps the client requests to MethodEnpoints
. It maps the incoming request to a method that starts with the handle+root
element of the message (handle+placeOrderRequest)
. In the endpoint class (OrderSeviceMethodEndpoint), a method with the name handleplaceOrderRequest
should be defined.
In this method, the parameter source includes the incoming message and input parameters to call order service could be extracted from this parameter, then the method calls to the orderService
method and wraps the outgoing message in the StringSource
that is to be sent back to the client:
public class OrderSeviceMethodEndpoint { OrderService orderService; public void setOrderService(OrderService orderService) { this.orderService = orderService; } public @ResponsePayload Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName,lName,refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); }
The endpoint mappings will be detailed in the later recipes.
HTTP is the most common Web-Service protocol. However, Web-Services are currently built on multiple transports, each with different scenarios.
JMS was included in Java 2, J2EE by Sun Microsystems in 1999. Using JMS, systems are able to communicate synchronously or asynchronously and are based on point-to-point and publish-subscribe models. SOAP over JMS inherits the JSM features and meets the following requirements:
Where asynchronous messaging is required
Where the message consumers are slower than the producers
To guarantee the delivery of messages
To have a publisher/subscriber(multiple) model
When sender/receiver might be disconnected
Spring Web-Services provide features to set up a Web-Service over JMS protocol that is built upon the JMS functionality in the Spring framework. In this recipe, how to set up a Spring-WS over JMS is presented.
In this recipe, the project's name is LiveRestaurant_R-1.4
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
spring-ws-support-2.0.1.RELEASE.jar
spring-test-3.0.5.RELEASE.jar
spring-jms-3.0.5.RELEASE.jar
junit-4.7.jar
xmlunit-1.1.jar
log4j-1.2.9.jar
jms-1.1.jar
activemq-core-4.1.1.jar
In this recipe, Apache ActiveMQ is used to set up a JMS server and to create JMS server-related objects (queue and broker are used here). Spring-WS family JARs provide a functionality to set up a Spring-WS and spring-jms
and jms
JARs provide the JMS functionality that the Spring-WS, over JMS, is built upon it.
Create an endpoint (OrderSeviceMethodEndpoint)
.
Configure the MessageListenerContainer, MessageListener
, and connectionFactory
in the Spring configuration file (applicationContext.xml
).
Configure MessageDispatcher
that includes the endpoint mappings inside applicationContext.xml
.
Run the recipe project using the following command:
mvn clean package
The following is the output once the project runs successfully:
INFO [main] (SaajSoapMessageFactory.java:135) -..
INFO [main] (DefaultLifecycleProcessor.java:330) -..
INFO [main] .. - ActiveMQ 4.1.1 JMS Message Broker (localhost)..
..
INFO [JMX connector] ..
INFO [main]..ActiveMQ JMS Message Broker ..started
INFO [main] ..- Connector vm://localhost Started
.....
Received response ....
<tns:placeOrderResponse ..><tns:refNumber>..</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest ....>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
........
DefaultMessageListenerContainer
listens to destinationName
(RequestQueue)
for incoming messages. When a message arrives, this listener will use the message factory (messageFactory)
to extract the message and use the dispatcher (messageDispatcher)
to dispatch the message to the endpoint (SimplePayloadEndpoint)
.............
In the application context, WebServiceMessageListener
is a listener inside MessageListenerContainer
. The message container uses connectionfactory
to connect to the destination (RequestQueue):
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory"> <property name="brokerURL" value="vm://localhost?broker.persistent=false"/> </bean> <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer"> <property name="connectionFactory" ref="connectionFactory"/> <property name="destinationName" value="RequestQueue"/> <property name="messageListener"> <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener"> <property name="messageFactory" ref="messageFactory"/> <property name="messageReceiver" ref="messageDispatcher"/> </bean> </property> </bean>
This listener uses message Dispatcher
and messageFactory
to receive incoming messages and to send outgoing SOAP messages. Inside messageDiapatcher
, endpoint's mapping is included, which sets the endpoint (SimplePayloadEndpoint)
and type of endpoint mapping (PayloadRootQNameEndpointMapping)
:
<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"> <property name="endpointMappings"> <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="defaultEndpoint"> <bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint"> <property name="orderService"> <bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/> </property> </bean> </property> </bean> </property> </bean>
The invoke
method from the endpoint (SimplePayloadEndpoint)
will be called when a request comes to the server, and the response will be returned to be sent back to the client:
public class SimplePayloadEndpoint implements PayloadEndpoint { OrderService orderService; public void setOrderService(OrderService orderService) { this.orderService = orderService; } public Source invoke(Source request) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); }
JmsTransportWebServiceIntegrationTest
is included in the project to load the application context, set up the JMS server, and test the Web-Service. However, these details are not discussed here. The client of JMS transport will be discussed in the next chapter.
The Creating a Web-Service client on JMS transport recipe discussed in Chapter 2, Building Clients for SOAP Web-Services and the Exposing Web-Services using JMS as the underlying communication protocol recipe discussed in Chapter 10,Spring Remoting.
HTTP is easy to understand and therefore has been most often defined and implemented, but it's clearly not the most suitable transport for Web-Services in any scenario.
Web-Service on E-mail transport can take advantage of store-and-forward messaging to provide an asynchronous transport for SOAP. In addition, there is no firewall concern on e-mail and those applications that are able to communicate together don't need web servers to set up a Web-Service. This allows SOAP, over mail transport, to be used in a number of scenarios where HTTP is not suitable.
The reasons why setting up a Web-Service over HTTP is not suitable and e-mail might be a solution as a transport protocol are listed as follows:
If a system is protected by a firewall, there is no control over the HTTP request/response, but e-mail is always is accessible.
If a system expects no request/response conventional model. For example, publish/subscriber model is required.
If a request takes too long to complete. For example, if the server has to run complex and time-consuming services, the client would get an HTTP timeout error. In such a scenario, Web-Service over e-mail is more appropriate.
In this recipe, setting up a Web-Service over E-mail transport is presented. To load the application context and test the Web-Service, a test class is used. This class also starts up and shuts down the server.
In this recipe, the project's name is LiveRestaurant_R-1.5
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
spring-ws-support-2.0.1.RELEASE.jar
spring-test-3.0.5.RELEASE.jar
mail-1.4.1.jar
mock-javamail-1.6.jar
junit-4.7.jar
xmlunit-1.1.jar
Setting up a mail server outside a system that is using JavaMail for testing purpose is difficult. Mock JavaMail addresses this issue and provides a pluggable component to the system using JavaMail. The system can use this component to send/receive e-mails against the temporary in-memory mailbox.
Create an endpoint (SimplePayloadEndpoint)
.
Configure MessageReceiver
and MessageDispatcher
that include endpoint mappings inside applicationContext.xml
.
Run the recipe project using the following command:
mvn clean package
The following is the output:
........
INFO [main] ...- Creating SAAJ 1.3 MessageFactory with SOAP 1.1 Protocol
..- Starting mail receiver [imap://[email protected]/INBOX]
....
Received response...
<tns:placeOrderResponse xmlns:tns="....">
<tns:refNumber>...</tns:refNumber></tns:placeOrderResponse>
...for request ..
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
......
Messages sent to an address will be saved in an inbox. The message receiver (messageReceiver)
monitors the inbox at continuous intervals and as soon as it detects a new E-mail, it reads the E-mail, extracts the message, and forwards the message to a message dispatcher (messageDispatcher)
. The message dispatcher will call the invoke
method inside its default endpoint (SamplePayloadEndpoint)
, and inside the handler method (invoke)
, the response will be sent back to the client.
When the application context is being loaded, MailMessageReceiver
starts up a mail receiver and its inbox folder (imap://[email protected]/INBOX)
, that is, a temporary in-memory inbox. After loading the application context, the messageReceiver
bean acts as a server monitor for the incoming messages based on a pluggable strategy (monotoringStrategy)
that monitors the INBOX
folder (imap://[email protected]/INBOX)
for new messages on pollingInterval
of 1000 ms. storeUri
is the location to be monitored for the incoming messages (imap://[email protected]/INBOX)
and transportUri
is the mail server for sending the responses:
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver"> <property name="messageFactory" ref="messageFactory"/> <property name="from" value="[email protected]"/> <property name="storeUri" value="imap://[email protected]/INBOX"/> <property name="transportUri" value="smtp://smtp.packtpubtest.com"/> <property name="messageReceiver" ref="messageDispatcher"/> <property name="session" ref="session"/> <property name="monitoringStrategy"> <bean class="org.springframework.ws.transport.mail.monitor.Pop3PollingMonitoringStrategy"> <property name="pollingInterval" value="1000"/> </bean> </property> </bean>
Inside messageDiapatcher
, endpoint mapping is included that sets the endpoint (SimplePayloadEndpoint)
and type of the endpoint mapping (PayloadRootQNameEndpointMapping)
:
<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"> <property name="endpointMappings"> <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="defaultEndpoint"> <bean class="com.packtpub.liverestaurant.service.endpoint.SimplePayloadEndpoint"> <property name="orderService"> <bean class="com.packtpub.liverestaurant.service.OrderServiceImpl"/> </property> </bean> </property> </bean> </property> </bean>
SimplePayloadEndpoint
receives a request and returns a fixed dummy response using OrderService
. When a request comes to the server, the invoke
method will be called and the response will be returned that is to be sent back to the client:
public class SimplePayloadEndpoint implements PayloadEndpoint { OrderService orderService; public void setOrderService(OrderService orderService) { this.orderService = orderService; } public Source invoke(Source request) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); }
To test this recipe, a webServiceTemplate
is used. We will discuss it in the next chapter.
MailTransportWebServiceIntegrationTest
is included in the project to load the application context, set up the mail server, and to test the Web-Service.
The Creating Web-Service client on E-mail transport recipe, discussed in Chapter 2,BuildingClients for SOAP Web-Services.
External HTTP servers might be able to provide several features, but they are not light and they need a configuration to set up.
Spring-WS provides a feature to set up an HTTP-based Web-Service using embedded Sun's JRE 1.6 HTTP server. The embedded HTTP server is a light-weight standalone server that could be used as an alternative to external servers. While configuration of the web server is a must in a conventional external server (web.xml)
, the embedded HTTP server doesn't need any deployment descriptor to operate and its only requirement is to configure an instance of the server through the application context.
In this recipe, setting up a Spring Web-Service on the embedded HTTP server is presented. Since there is no external HTTP server, a Java class is used to load application context and start up the server.
In this recipe, the project's name is LiveRestaurant_R-1.6
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
Copy the service contract (OrderService.wsdl)
from the resource folder.
Create a service and an implementation of it and annotate its implementation with @Service("serviceName")
(OrderSevice,OrderServiceImpl)
.
Configure the service in the application context (applicationContext)
that is to be scanned and detected automatically.
Configure the embedded HTTP server inside the application context.
Add a Java class with the main method to load the application context to set up the embedded HTTP server.
Run the server using the following command:
mvn clean package exec:java
From LiveRestaurant_R-1.6-Client
, run the following command:
mvn clean package exec:java
The following is the output when the server runs successfully:
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
<tns:customer>
.......
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
The following is the client-side output:
<tns:placeOrderResponse ...><refNumber>order-John_Smith_1234</refNumber></tns:placeOrderResponse>>
......
.....
In the application context, SimpleHttpFactoryBean
creates a simple HTTP server (from embedded Sun's JRE 1.6) and it starts the HTTP server on initialization and stops it on destruction.
The HTTP server that has a context property sets up a Web-Service with the service class (orderServiceImpl)
set as the endpoint and specifies the URL defined by the properties inside the context (localhost:3478/OrderService)
. This service interface is registered within the context property.
However, the service implementation is autodetected using component-scan. HttpInvokerProxyFactoryBean
creates a client's proxy for a specific server URL.
<context:annotation-config /> <context:component-scan base-package="com.packtpub.liverestaurant.service.endpoint" /> <bean id="httpServer" class="org.springframework.remoting.support.SimpleHttpServerFactoryBean"> <property name="contexts"> <util:map> <entry key="/OrderService"> <bean class="org.springframework.remoting.httpinvoker.SimpleHttpInvokerServiceExporter"> <property name="serviceInterface" value="com.packtpub.liverestaurant.service.endpoint.IOrderServiceEndPoint" /> <property name="service" ref="orderServiceImpl" /> </bean> </entry> </util:map> </property> <property name="port" value="3478" /> <property name="hostname" value="localhost" /> </bean>
IOrderServiceEndPointImpl
and IOrderServiceEndPoint
are simple service interface and implementation classes. IOrderServiceEndPointImpl
is annotated by @Service
(orderServiceImpl)
and is to be detected as a service implementation.
package com.packtpub.liverestaurant.service.endpoint; public interface OrderService { String invoke(String request) throws Exception; } package com.packtpub.liverestaurant.service.endpoint; import org.apache.log4j.Logger; import org.springframework.stereotype.Service; @Service("orderServiceImpl") public class OrderServiceImpl implements OrderService { static Logger logger = Logger.getLogger(OrderServiceImpl.class); private static final String responseContent = "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><refNumber>Order Accepted!</refNumber></tns:placeOrderResponse>"; public String invoke(String request) throws Exception { logger.info("invoke method request:"+request); return responseContent; } }
ServerStartUp.java
is used to load the application context and start up the server:
package com.packtpub.liverestaurant.server; public class ServerStartUp { public static void main(String[] args) throws IOException { ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("/applicationContext.xml"); System.out.println(appContext); char c; // Create a BufferedReader using System.in BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Enter any character to quit."); c = (char) br.read(); appContext.close(); }
HTTP is most often used as a Web-Service transport protocol. However, it is not able to meet the asynchronous communication requirements.
Web-Service on XMPP transport is capable of asynchronous communication in which a client doesn't need to wait for a response from a service; instead, the service sends the response to the client when the process is completed. Spring-WS 2.0 includes XMPP (Jabber) support in which a Web-Service can communicate over the XMPP protocol. In this recipe, setting up a Spring-WS on XMPP transport is presented. Since there is no external HTTP server, a test class is used to load the application context.
In this recipe, the project's name is LiveRestaurant_R-1.7
, which has the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
spring-ws-support-2.0.1.RELEASE.jar
spring-test-3.0.5.RELEASE.jar
junit-4.7.jar
xmlunit-1.1.jar
smack-3.1.0.jar
Create an endpoint (SamplePlayLoadEndPoint).
Configure connection to the XMPP server in the application context (applicationContext.xml)
.
Configure the message receiver in the application context.
Run the following command:
mvn clean package
The following is the response received:
<placeOrderRequest xmlns="..."><id>9999</id></placeOrderRequest>
...
for request
...<placeOrderRequest xmlns="...."><id>9999</id></placeOrderRequet>...
In the application context, the messageFactory
bean is responsible for creating the incoming and outgoing SOAP messages. The messageReceiver
bean acts as a server, using a connection (to XMPP server:google talk)
, and listens to the host on a specific service with a username and password.
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/> <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean"> <property name="host" value="talk.google.com"/> <property name="username" value="[email protected]"/> <property name="password" value="yourPassword"/> <property name="serviceName" value="gmail.com"/> </bean> <bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver"> <property name="messageFactory" ref="messageFactory"/> <property name="connection" ref="connection"/> <property name="messageReceiver" ref="messageDispatcher"/> </bean>
Once the message is sent by the client, it will be forwarded to the endpoint (SamplePlayLoadEndPoint
that is configured within messageDispatcher)
by the message dispatcher and the response will be returned to the client:
<bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"> <property name="endpointMappings"> <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="defaultEndpoint"> <bean class="com.packtpub.liverestaurant.service.endpoint.SamplePlayLoadEndPoint"/> </property> </bean> </property> </bean>
Webservicetemplate
is used here as a client; it will be discussed in the next chapter.
SamplePlayLoadEndPoint
just receives a request and returns a response:
public class SamplePlayLoadEndPoint implements PayloadEndpoint { static Logger logger = Logger.getLogger(SamplePlayLoadEndPoint.class); public Source invoke(Source request) throws Exception { return request; }
A test class is included in the project to load the application context, set up the XMPP Web-Service server, and test the Web-Service.
The Creating Web-Service client on XMPP transport recipe discussed in Chapter 2,Clients for SOAP Web-Services.
Generating WSDL and XSD contracts from Java code and setting up a Web-Service is called contract-last development. The major drawback to this approach is the contracts (WSDL or XSD) of the Web-Service could eventually change if there are any changes in Java classes. In this way, the client side has to update the client-side classes and that always is not favorable. The contract-first approach was introduced as an alternative to tackle the contract-last's bottleneck. In the contract-first approach, the contract (WSDL or schema) are primary artifacts to set up a Web-Service.
Some of the advantages of the contract-first approach over contract-last are as follows:
Performance: In contract-last, some extra data, that is, serialization of Java code might be exchanged between client and server, which decreases the performance, while contract-last precisely exchanges the required data and maximizes the performance.
Consistency: Different vendors may generate different WSDL in the contract-last approach, while the contract-first approach eliminates this problem by standing on the same contract.
Versioning: Changing the version of a contract-last Web-Service means changing Java classes in both client and server side and that might eventually be expensive in case there are a lot of clients that call a Web-Service, while in contract-first, since the contract is decoupled from implementation, versioning could be simply done by adding a new method implementation in the same endpoint class or using a stylesheet to convert an old message format into new message format.
Maintenance/enhancement cost: Changing only a contract is much cheaper than changing Java code in both client and server side. In this recipe, we will discuss how to set up a contract-first Web-Service using Spring-WS.
In this recipe, the project's name is LiveRestaurant_R-1.8
, with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
jdom-1.0.jar
Copy the data contract (orderService.xsd)
from the resources folder.
Create an endpoint (OrderEndpoint)
.
Configure the auto-detection of the endpoint using the component scan in the server Spring configuration file (spring-ws-servlet.xml)
.
Configure the dynamic generation of WSDL from the data contract (orderService.xsd)
.
Run the server using the following command:
mvn clean package tomcat:run
Browse to the following link to see the WSDL:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
mvn clean package
The following is the output when the server runs successfully:
Sent response....
<tns:placeOrderResponse xmlns:tns="...."><tns:refNumber>tns:refNumber>order-John_S
mith_9999</tns:refNumber></tns:refNumber></
tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="....">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
....
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
The steps of this recipe are the same as that of the recipe Simplifying the creation of a Web-Service using MessageDispatcherServlet, except the implementation of endpoint handling methods.
This annotation serves as a specialization of @Component
, allowing for the implementation classes to be autodetected through classpath scanning, which is configured in the server application context file (spring-ws-servlet.xml)
:
<context:component-scan base-package="com.packtpub.liverestaurant.service"/> <sws:annotation-driven/>
OrderEndpoint
is the endPoint
of this recipe and the @Endpoint
annotation is also the same as @service
, allowing for the implementation classes to be autodetected through classpath scanning. A request with the root element placeOrderRequest
(localPart = "placeOrderRequest")
and the namespace http://www.packtpub.com/liverestaurant/OrderService/schema
will be forwarded to call the corresponding method (handlePlaceOrderRequest)
.
@Endpoint public class OrderEndpoint { private static final Log logger = LogFactory.getLog(OrderEndpoint.class); private static final String NAMESPACE_URI = "http://www.packtpub.com/liverestaurant/OrderService/schema"; private OrderService orderService; @Autowired public OrderEndpoint(OrderService orderService) { this.orderService = orderService; } @PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest") @ResponsePayload public Source handlePancelOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception { String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent(); String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent(); String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent(); return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName,lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); } }
Other details about annotations and how the request will be mapped to an endpoint method are contained in this chapter.
The following setting in the spring-ws-servlet.xml
file causes the application to automatically generate the WSDL file from the data contract (orderService.xsd)
.
<sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema"> <sws:xsd location="/WEB-INF/orderService.xsd"/> </sws:dynamic-wsdl>
Even though WSDL can be generated automatically from the data contract (XSD), Spring-WS recommends avoiding autogeneration of WSDL for these reasons:
To keep consistency between releases (there might be slight differences among autogenerated WSDLs for different versions)
Autogeneration of WSDL is slow, although once generated, WSDL will be cached and used later.
Therefore, Spring-WS recommends, while developing, autogenerate WSDL once via the browser and save it and use static WSDL to expose the service contract.
The recipes Setting up an endpoint by annotating the payload-root, Simplifying the creation of a Web-Service using MessageDispatcherServlet, discussed in this chapter and the Creating a Web-Service client on HTTP transport recipe, discussed in Chapter 2,Building Clients forSOAP Web-Services.
Also see the recipes discussed in Chapter 10, Spring Remoting, to find out how to set up contract-last Web-Services.
This recipe demonstrates a very simple endpoint mapping that maps a Web-Service request to a Java class method.
In this recipe, the project's name is LiveRestaurant_R-1.9
, with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-12.9.jar
The steps of this recipe are the same as that of the previous recipe, Setting up a contract-first Web-Service, except that the registration of the endpoint, that is, method endpoint mapping and is configured in spring-ws-servlet.xml
.
Define an endpoint (OrderSeviceMethodEndpoint)
based on the method mapping standard (SimpleMethodEndpointMapping)
.
Configure the method endpoint mapping in spring-ws-servlet.xml
.
Run the mvn clean package tomcat:run
command and browse to see the WSDL:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window, go to Liverestaurant_R-1.9-Client
, and run the following command:
mvn clean package exec:java
Here is the server-side output:
Sent response ..
<tns:placeOrderResponse xmlns:tns="..."><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
SimpleMethodEndpointMapping
maps from the local name of the request payload (placeOrderRequest)
to the methods of the POJO classes. Here is a sample of the request payload (note the local name of the request payload):
<tns:placeOrderRequest ...>
<tns:order>
......
</tns:order>
</tns:placeOrderRequest>
The endpoint bean is registered using the endpoints
property. This property tells you that there should be a method in the endpoint
class (OrderServiceEndpoint)
with a name that starts with methodPrefix(handle)
and ends with the request payload local name (placeOrderRequest)
. This increases the flexibility of the endpoint naming by using the configuration in spring-ws-servlet.xml:
<bean class="org.springframework.ws.server.endpoint.mapping.SimpleMethodEndpointMapping"> <property name="endpoints"> <ref bean="OrderServiceEndpoint"/> </property> <property name="methodPrefix" value="handle"></property> <property name="interceptors"> <list> <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"> <property name="logRequest" value="true" /> <property name="logResponse" value="true" /> </bean> </list> </property> </bean> <bean id="OrderServiceEndpoint" class="com.packtpub.liverestaurant.service.endpoint.OrderSeviceMethodEndpoint"> </bean>
The endpoint method name should match the handle+request
message root name (handleplaceOrderRequest)
. In the body of the method, we should process the request and finally return the response in the form of javax.xml.transform.Source:
public class OrderSeviceMethodEndpoint { private OrderService orderService; @Autowired public void setOrderService(OrderService orderService) { this.orderService = orderService; } public @ResponsePayload Source handleplaceOrderRequest(@RequestPayload Source source) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); } }
Spring-WS simplifies the creation of complex Web-Services further by its annotation features and reduces the code and configuration in XML.
In this recipe, the project's name is LiveRestaurant_R-1.10
, with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-12.9.jar
The steps of this recipe are the same as that of Setting up a contract-first Web-Service and here we want to describe the endpoint mapping using annotation in the endpoint
class.
Run the following command:
mvn clean package tomcat:run
Browse to the following link to see the WSDL:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window, go to LiveRestaurant-1.10-Client
, and run the following command:
mvn clean package exec:java
Here is the server-side output:
Sent response ..
<tns:placeOrderResponse xmlns:tns="..."><tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>order-9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
By including component scan and annotation-driven settings in the Spring-WS configuration file (spring-ws-servlet.xml)
, the Spring container will scan the entire package for endpoints, services, and dependencies to inject and autowire each other to build the Web-Service blocks. You cannot see the adapters and other handlers here, since the container smartly picks the right/default adapter, dynamically (messageDispatcher runs support method of an adapter from a list of existing adapters for the endponit, and if support method returns true
, that adapter is the right adapter):
<context:component-scan base-package="com.packtpub.liverestaurant.service"/> <sws:annotation-driven/> <sws:dynamic-wsdl id="OrderService" portTypeName="OrderService" locationUri="http://localhost:8080/LiveRestaurant/spring-ws/OrderService" targetNamespace="http://www.packtpub.com/liverestaurant/OrderService/schema"> <sws:xsd location="/WEB-INF/orderService.xsd"/> </sws:dynamic-wsdl>
The @Endpoint
annotation of OrderSeviceAnnotationEndpoint
makes it an endpoint, with PayloadRootAnnotationMethodEndpointMapping
, with the exact pointers to the method-endpoint mapping with the method-level annotations:
@Endpoint public class OrderSeviceAnnotationEndpoint { private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema"; private OrderService orderService; @Autowired public OrderSeviceAnnotationEndpoint(OrderService orderService) { this.orderService = orderService; } @PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS) public @ResponsePayload Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); } @PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS) public @ResponsePayload Source handleCancelOrderRequest(@RequestPayload Source source) throws Exception { //extract data from input parameter boolean cancelled =true ; return new StringSource( "<tns:cancelOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>"); }
@PayloadRoot
helps the MessageDispatcher
to map the request to the method, with the help of an argument annotation, @RequestPayload
, which specifies the exact message payload part of the entire SOAP message as an argument into the method (it finds the method by root element of a request equal to localPart
, for example, placeOrderRequest
or placeCancelRequest). @RequestPayload
tells the container that the argument RequestPayload
is to be extracted from the SOAP message and injected to the method as an argument at runtime.
The return type annotation, @ResponsePayload
, instructs MessageDispatcher
that the instance of javax.xml.transform.Source
is ResponsePayload
. The smart Spring-WS framework detects the type of these objects at runtime and delegates to the appropriate PayloadMethodProcessor
. In this case, it is SourcePayloadMethodProcessor
, since the input argument and the return value are of the type javax.xml.transform.Source
.
Using HTTP transport information inside the XML messages for routing messages to endpoints mixes data and operation together, and these messages will be replied to for the requested client.
WS-Addressing standardizes routing mechanism by separating routing data and including it inside the SOAP headers. WS-Addressing may use its own metadata instead of using HTTP transport data for endpoint routing. In addition, a request from a client may return to a different client in WS-Addressing. For example, considering the following request from a client, the client side can set ReplyTo
to its own address and FaultTo
to admin the endpoint address. Then, the server will send successful messages to the client and fault messages to the admin address [<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
.
<SOAP-ENV:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:To>server_uri</wsa:To>
<wsa:Action>action_uri</wsa:Action>
<wsa:From>client_address </wsa:From>
<wsa:ReplyTo>client_address</wsa:ReplyTo>
<wsa:FaultTo>admen_uri </wsa:FaultTo>
<wsa:MessageID>..</wsa:MessageID>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderRequest>....</tns:placeOrderReques>
</SOAP-ENV:Body></SOAP-ENV:Envelope>]
In this recipe, we will set up a Spring-WS using WS-Addressing.
In this recipe, the project's name is LiveRestaurant_R-1.11
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-12.9.jar
The steps of this recipe are the same as that of Setting up an endpoint by annotating the payload-root, except for the endpoint class. So, follow the steps of the mentioned recipe and define a new endpoint with WS-Addressing standards.
Run the following command:
mvn clean package tomcat:run
To test, open a new command window to Liverestaurant_R-1.11-Client
and run the following command:
mvn clean package exec:java
The following is the server-side output:
Sent response [<SOAP-ENV:Envelope ...><SOAP-ENV:Header...>
<wsa:To ...>http://www.w3.org/2005/08/addressing/anonymous</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReqResponse</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
<wsa:RelatesTo>urn:uuid:2beaead4-c04f-487c-86fc-caab64ad8461</wsa:RelatesTo>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
<tns:placeOrderResponse ...><tns:refNumber>order-John_Smith_1234</tns:refNumber></tns:placeOrderResponse>
</SOAP-ENV:Body></SOAP-ENV:Envelope>...
for request <SOAP-ENV:Envelope ..><SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header><SOAP-ENV:Body>
<tns:placeOrderRequest ...>
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
...
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
</SOAP-ENV:Body></SOAP-ENV:Envelope>
Same as the previous recipe, Setting up an endpoint by annotating the payload-root, the incoming WS-Addressing SOAP messages will be forwarded to the endpoint (OrderEndpoint
, which is autodetected by @Endpoint)
. As you can see from the output, a header is added to the SOAP envelop that WS-Addressing uses for mapping and dispatching purposes of the endpoint method.
<SOAP-ENV:Header ...>
<wsa:To SOAP-..>http://www.packtpub.com/liverestaurant/OrderService/schema</wsa:To>
<wsa:Action>http://www.packtpub.com/OrderService/OrdReq</wsa:Action>
<wsa:MessageID>...</wsa:MessageID>
</SOAP-ENV:Header>
In this recipe, the server applies AnnotationActionEndpointMapping
, which uses @Action
(http://www.packtpub.com/OrderService/OrdReq). @Action
is similar to @PayloadRoot
for recognizing the handling methods (handleOrderRequest)
in the endpoint (OrderEndpoint)
.
@Endpoint public class OrderEndpoint { private OrderService orderService; @Autowired public void setOrderService(OrderService orderService) { this.orderService = orderService; } @Action("http://www.packtpub.com/OrderService/OrdReq") public @ResponsePayload Source handleOrderRequest(@RequestPayload Source source) throws Exception { //extract data from input parameter String fName="John"; String lName="Smith"; String refNumber="1234"; return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>"+orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); } }
The recipe Creating Web-Service client for WS-Addressing endpoint, discussed in Chapter 2, Building Clients for SOAP Web-Services, and the recipe Setting up an endpoint by annotating the payload root, discussed in this chapter.
Spring-WS allows us to extract the passed parameters in the endpoint
method's signature using annotations with the XPath expressions. For example, in the endpoint
method's handleOrderRequest
(@RequestPayload Source source)
, if you want to find the value of any element in the source object, you have to use Java API to extract the value. You can eliminate using Java API in the handler method by using XPath in the method's signature to extract the data from the incoming XML data, as shown as follows: handleOrderRequest(@XPathParam("/OrderRequest/message") String message)
.
This recipe illustrates the usage of XPath expressions in endpoint mapping with the help of annotation.
In this recipe, the project's name is LiveRestaurant_R-1.12
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-12.9.jar
The steps of this recipe are the same as that of Setting up an endpoint by annotating the payload-root, except for the implementation of endpoint handling methods. So, follow the steps of the mentioned recipe and use XPath expressions to extract data from incoming message and create a response.
Run the following command from LiveRestaurant_R-1.12:
mvn clean package tomcat:run
Browse to the following link to see the Web-Service service contract:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window, go to LiveRestaurant_R-1.12-Client
, and run the following command:
mvn clean package exec:java
s
The following is the server-side output:
Sent response ..
<tns:placeOrderResponse xmlns:tns="">
<tns:refNumber>order-John_Smith_9999</tns:refNumber>
</tns:placeOrderResponse>
...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<order>
<refNumber>9999</refNumber>
<customer>
......
</customer>
<dateSubmitted>2008-09-29T05:49:45</dateSubmitted>
<orderDate>2014-09-19T03:18:33</orderDate>
<items>
<type>Snacks</type>
<name>Pitza</name>
<quantity>2</quantity>
</items>
</order>
</tns:placeOrderRequest>
...
Sent response...
<tns:cancelOrderResponse xmlns:tns="...">
<tns:cancelled>true</tns:cancelled>
</tns:cancelOrderResponse>
...
for request ...
<tns:cancelOrderRequest xmlns:tns="...">
<refNumber>9999</refNumber>
</tns:cancelOrderRequest>
Passing the method parameter is the same as the recipe Setting up an endpoint by annotating the payload-root, except that it uses @XPathParam
, which specifies the path of the data in a message that is to be passed as an argument into the method. Here XpathParamMethodArgumentResolver
is responsible for extracting the value from the message and passing it to the method.
The annotation XpathParam
helps the MethodArgumentResolvers (XpathParamMethodArgumentResolver)
to extract information out of the XML and binds a node value to a method argument (using // cause
, the whole message is searched recursively, for example, //lName
searches the whole placeRequestRequest
message). The same implementation is used for the method cancelOrderRequest:
@Endpoint public class OrderEndpoint { private final String SERVICE_NS = "http://www.packtpub.com/liverestaurant/OrderService/schema"; private OrderService orderService; @Autowired public OrderEndpoint(OrderService orderService) { this.orderService = orderService; } @PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS) public @ResponsePayload Source handleOrderRequest(@XPathParam("//fName") String fName,@XPathParam("//lName") String lName,@XPathParam("//refNumber") String refNumber) throws Exception { return new StringSource( "<tns:placeOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><tns:refNumber>" + orderService.placeOrder(fName, lName, refNumber)+"</tns:refNumber></tns:placeOrderResponse>"); } @PayloadRoot(localPart = "cancelOrderRequest", namespace = SERVICE_NS) public @ResponsePayload Source handleCancelOrderRequest(@XPathParam("//refNumber") String refNumber) throws Exception { boolean cancelled = orderService.cancelOrder(refNumber); return new StringSource( "<tns:cancelOrderResponse xmlns:tns="http://www.packtpub.com/liverestaurant/OrderService/schema"><cancelled>"+(cancelled?"true":"false")+"</cancelled></tns:cancelOrderResponse>"); }
The method argument can be any of the following:
boolean
or Boolean
double
or Double
String
Node
NodeList
The implementation of the endpoint requires us to get the incoming XML messages and extract its data. In Java, there are various methods (W3C DOM, SAX, XPath, JAXB, Castor, XMLBeans, JiBX
, or XStream)
for extracting data from an input XML message, but most of them are not language-neutral.
DOM was created to be language-neutral and initially used for JavaScript manipulation of HTML pages. In Java, W3C DOM library is provided to interact with XML data. Classes, such as org.w3c.dom.Document, org.w3c.dom.Element, org.w3c.dom.Node
, and org.w3c.dom.Text
from W3C DOM library, are for extracting data from an input XML message.
In this recipe, W3C DOM is used to extract the data from incoming messages.
In this recipe, the project's name is LiveRestaurant_R-1.13
, with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
The steps of this recipe are the same as that of the recipe Setting up an endpoint by annotating the payload-root, except for the implementation of the endpoint-handling methods. So, follow the steps of the mentioned recipe and use DOM to extract data from the incoming message and create the response.
Run the command mvn clean package tomcat:run
and browse to the following link:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window and run the following command:
mvn clean package exec:java
The following is the server-side output:
Sent response ....
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber></placeOrderResponse>
...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
.... </tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
,
Passing the method parameter is the same as the recipe Setting up an endpoint by annotating the payload-root, except that we use @RequestPayload
, which specifies the DOM element of data in a message to be passed as an argument into the method. Here, DomPayloadMethodProcessor
is responsible for extracting the value from the message and passing it to the method. Since the return type which is specified by @ResponsePayload
is also a DOM element type, DomPayloadMethodProcessor
is being used as return handler.
The @PayloadRoot
annotation informs Spring-WS that the handleCancelOrderRequest
method is a handling method for XML messages. The sort of message that this method can handle is indicated by the annotation values (the @RequestPayload
element tells it is of the DOM element type). In this case, it can handle XML elements that have the placeOrderRequest
local part and the http://www.packtpub.com/liverestaurant/OrderService/schema
namespace.
@PayloadRoot(namespace = NAMESPACE_URI, localPart = "placeOrderRequest") @ResponsePayload public Element handlePlaceOrderRequest(@RequestPayload Element placeOrderRequest) throws Exception { String refNumber=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "refNumber") .item(0).getTextContent(); String fName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "fName") .item(0).getTextContent(); String lName=placeOrderRequest.getElementsByTagNameNS(NAMESPACE_URI, "lName") .item(0).getTextContent();
The preceding code extracts the elements refNumber, fName
, and lName
from the incoming XML message (placeOrderRequest)
via the method getElementsByTagNameNS
. Then, it finds and returns the text content of the first item in the refNumber, fName
, and lName
elements (by item(0).getTextContent())
.
The following part of the code creates an outgoing XML message by creating the placeOrderResponse
element (using document.createElementNS)
. Then, it creates the child element refNumber
(using document.createElementNS)
and creates the text of this element (using createTextNode and appendChild)
. Then, it appends the refNumber
element to the response element placeOrderResponse
(using the appendChild
method):
Document document = documentBuilder.newDocument(); Element responseElement = document.createElementNS(NAMESPACE_URI, "placeOrderResponse"); Element canElem=document.createElementNS(NAMESPACE_URI,"refNumber"); Text responseText = document.createTextNode(orderService.placeOrder(fName, lName, refNumber)); canElem.appendChild(responseText); responseElement.appendChild(canElem); return responseElement;
The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter and the recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,Building Clients for SOAP Web-Services.
Implementation of endpoints requires us to get the incoming XML messages and extract its data. DOM can fetch the data from an XML document, but it is slow and memory consuming and has very basic features.
JDOM document is not built into the memory; it is built on demand (lazy initialization design pattern). In addition, JDOM makes navigating through the document tree or manipulating the elements easier by providing a standard Java-based collection interface. In this recipe, JDOM is used to extract data from incoming messages.
In this recipe, the project's name is LiveRestaurant_R-1.14
, with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
jdom-1.0.jar
log4j-1.2.9.jar
jaxen-1.1.jar
xalan-2.7.0.jar
The steps of this recipe are the same as that of the Setting up an endpoint by annotating the payload-root recipe, except for the implementation of the endpoint-handling methods. So, follow the steps of the aforementioned recipe, use JDOM to extract the data from incoming message, and create the response.
mvn exec:java exec:java
The following is the server-side output:
Sent response ...
<tns:placeOrderResponse xmlns:tns="...">
<tns:refNumber>order-John_Smith_1234</tns:refNumber>
</tns:placeOrderResponse>....
for request ....
<tns:placeOrderRequest xmlns:tns="....">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
It works in the same way as explained in the previous recipe, except that it uses JDOM
in its method implementation.
The following part of the code extracts the values refNumber, fName
, and lName
from the incoming XML message (placeOrderRequest)
by using namespace and XPath object:
Namespace namespace = Namespace.getNamespace("tns", NAMESPACE_URI); XPath refNumberExpression = XPath.newInstance("//tns:refNumber"); refNumberExpression.addNamespace(namespace); XPath fNameExpression = XPath.newInstance("//tns:fName"); fNameExpression.addNamespace(namespace); XPath lNameExpression = XPath.newInstance("//tns:lName"); lNameExpression.addNamespace(namespace); String refNumber = refNumberExpression.valueOf(placeOrderRequest); String fName = fNameExpression.valueOf(placeOrderRequest); String lName = lNameExpression.valueOf(placeOrderRequest);
The following part of the code creates an outgoing message by creating the placeOrderResponse
element (using new Element(...))
. Then, it creates the child element refNumber
(using new Element(...))
and creates the text of this element (using setText(...))
. Then, it appends the message element to the response element placeOrderResponse
(using the addContent
method):
Namespace resNamespace = Namespace.getNamespace("tns", NAMESPACE_URI); Element root = new Element("placeOrderResponse", resNamespace); Element message = new Element("refNumber", resNamespace); message.setText(orderService.placeOrder(fName, lName, refNumber)); root.addContent(message); Document doc = new Document(root); return doc.getRootElement();
The recipes Setting up an endpoint by annotating the payload-root and Handling incoming XML Messages using DOM, discussed in this chapter.
The recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,Building Clients for SOAP Web-Services.
Java Architecture for XML Binding (JAXB) is a Java standard for Object-XML marshalling. JAXB defines a programmer API for reading and writing Java objects to / from XML documents. The object-XML mapping is generally annotated in classes. JAXB provides a set of useful annotations with the default values for most of them that make this marshalling an easy job.
This recipe demonstrates how to handle the incoming XML message in a Web-Service using JAXB in a very simple way. For simplicity and a continuation from the previous recipes, the same recipes are re-used with little improvements in converting the XML schema into domain classes to demonstrate the usage of JAXB.
In this recipe, the project's name is LiveRestaurant_R-1.15
and has the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
The steps of this recipe are the same as that of the recipe Setting up an endpoint by annotating the payload-root, except for the implementation of the endpoint handling methods. So, follow the steps of the aforementioned recipe and use JAXB Marshaller/Un-Mashaller to convert payload into/from POJO.
First, we define a set of domain objects we need to marshal to/from XML from the data contract OrderService.xsd
(refer to the recipe Marshalling with JAXB2, discussed in Chapter 6,
Change the implementation of the endpoint (OrderEndpoint)
to use JAXB.
Run the following command:
mvn clean package tomcat:run
Browse to the following link:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window to Liverestaurant_R-1.15-Client
and run the following command:
mvn clean package exec:java
The following is the server-side output:
Sent response ....
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>....
....
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
........
</tns:customer>
<tns:dateSubmitted>2008-09-29T05:49:45</tns:dateSubmitted>
<tns:orderDate>2014-09-19T03:18:33</tns:orderDate>
<!--1 or more repetitions:-->
<tns:items>
<tns:type>Snacks</tns:type>
<tns:name>Pitza</tns:name>
<tns:quantity>2</tns:quantity>
</tns:items>
</tns:order>
</tns:placeOrderRequest>
In the preceding code, the XML is bound with Java classes at runtime using JAXB
. The incoming XML is converted into Java objects (unmarshalling) and after processing the objects, the resultant objects are marshalled back to XML before returning to the caller:
@PayloadRoot(localPart = "placeOrderRequest", namespace = SERVICE_NS) public @ResponsePayload Source handlePlaceOrderRequest(@RequestPayload Source source) throws Exception { PlaceOrderRequest request = (PlaceOrderRequest) unmarshal(source, PlaceOrderRequest.class); PlaceOrderResponse response = new PlaceOrderResponse(); String refNumber=request.getOrder().getRefNumber(); String fName=request.getOrder().getCustomer().getName().getFName(); String lName=request.getOrder().getCustomer().getName().getLName(); response.setRefNumber(orderService.placeOrder(fName,lName,refNumber)); return marshal(response); } private Object unmarshal(Source source, Class clazz) throws JAXBException { JAXBContext context; try { context = JAXBContext.newInstance(clazz); Unmarshaller um = context.createUnmarshaller(); return um.unmarshal(source); } catch (JAXBException e) { e.printStackTrace(); throw e; } } private Source marshal(Object obj) throws JAXBException { JAXBContext context = JAXBContext.newInstance(obj.getClass()); return new JAXBSource(context, obj); }
The JAXB
context binds the Java classes passed via the constructor with the incoming XML, with the help of annotations in the classes at runtime, which instructs the unmarshaller to instantiate and load data from the XML tags into the objects. The objects are now passed to the service classes (OrderServiceImpl)
for processing:
public class OrderServiceImpl implements OrderService { @Service public class OrderServiceImpl implements OrderService { public String placeOrder( String fName,String lName,String refNumber){ return "order-"+fName+"_"+lName+"_"+refNumber; } public boolean cancelOrder( String refNumber ){ return true; }
This approach allows the developer to work with Java objects instead of XML code with simple marshalling technology.
The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter.
The recipe Marshalling with JAXB2, discussed in Chapter 6,Marshalling and Object-XMLMapping (OXM) — Converting POJO to/from XML messages using Marshallers andUn-Marshallers.
Data contract is a basic concept used to set up a Spring-WS. However, validation is a basic requirement before a SOAP message sends/replies on the server side/client side.
Spring-WS supports validation of messages on the server side as well as the client side. In this recipe, server-side validation is applied and when the incorrect request comes to the server or the incorrect response replays from the server to the client, it throws an exception.
In this recipe, the project's name is LiveRestaurant_R-1.16
with the following Maven dependencies:
spring-ws-core-2.0.1.RELEASE.jar
log4j-1.2.9.jar
The steps of this recipe are the same as that of Handling the incoming XML messages using DOM, except for the validation of request/response message.
Modify spring-ws-servlet.xml
to include PayloadValidatingInterceptor
.
Run the following command:
mvn clean package tomcat:run
Browse to the following link:
http://localhost:8080/LiveRestaurant/OrderService.wsdl
To test, open a new command window to Liverestaurant_R-1.16-Client
and run the following command:
mvn clean package exec:java
The following is the server-side output:
Sent response [...
<placeOrderResponse xmlns="...">
<refNumber>order-John_Smith_1234</refNumber>
</placeOrderResponse>...
for request ...
<tns:placeOrderRequest xmlns:tns="...">
<tns:order>
<tns:refNumber>9999</tns:refNumber>
<tns:customer>
<tns:addressPrimary>
.....
</tns:addressPrimary>
.......
</tns:customer>
........
</tns:order>
</tns:placeOrderRequest>
WARN [http-8080-1] (AbstractFaultCreatingValidatingInterceptor.java:156) - XML validation error on request: cvc-complex-type.2.4.a: Invalid content was found s
tarting with element 'tns:address'. One of '{"http://www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.
........ Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http:
//www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>]
spring-ws-servlet.xml
is almost the same, as described in the recipe Handling the incoming XML messages using DOM, except that it includes the interceptor that uses schema for validation, validateRequest
, and validateResponse
.
<sws:interceptors> <bean class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor"> <property name="schema" value="/WEB-INF/OrderService.xsd"/> <property name="validateRequest" value="true"/> <property name="validateResponse" value="true"/> </bean> <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"> </bean> </sws:interceptors>
When running the client, two requests will be sent to the server. The first one will be processed and the response will be sent back to the client, while the second one contains the wrong element (address instead of addressPrimary)
that will send the faulty response back:
Sent response....
<faultcode>SOAP-ENV:Client</faultcode><faultstring xml:lang="en">Validation error</faultstring><detail><spring-ws:ValidationErr
or xmlns:spring-ws="http://springframework.org/spring-ws">cvc-complex-type.2.4.a: Invalid content was found starting with element 'tns:address'. One of '{"http: //www.packtpub.com/liverestaurant/OrderService/schema":addressPrimary}' is expected.</spring-ws:ValidationError></detail></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP
-ENV:Envelope>]
....
The recipe Setting up an endpoint by annotating the payload-root, discussed in this chapter.
The recipe Creating a Web-Service client on HTTP transport, discussed in Chapter 2,BuildingClients for SOAP Web-Services.