Intention
Thinking of Web Services, REST, mostly combined with JSON as the transport medium, currently is everywhere. Having done a lot for the acceptance of Web Services and furthermore simplifying the usage of Web Services themselves, there nevertheless seems to be a strong tendency to overuse REST.
How can REST be overused? This obviously depends on the use case, there are several use cases REST is not the definite answer:
- A strictly defined format of input and output data (the data transfer format), which can easily be validated
- A backend to backend communication, where no REST-affine language like JavaScript is used
- Communication between legacy systems which would have to have REST adapters built and already have SOAP-compliant adapters
- Pure service-oriented components, as well as (pre-)ESB and EAI systems
So, REST is not the answer to all Web Service problems. However, it is often proposed as this. To my experience, this is mainly caused by the fact that people think SOAP is just too complicated, not suitable for light services and just too much overhead. Even though that might be true with respect to WS-*, it is really quite easy to setup Web Services server and clients (and tests) which use SOAP – with no hassle.
The User Web Service – A practical approach to SOAP Web Services
We will follow a really practical approach, ie. many pictures, almost no theoretical background. However, whenever necessary, a link will be present to sites explaining the theoretical background.
In this post, Spring Web Services 2 is used for the server and client side. Even though there are “easier” frameworks around, which make creation of (SOAP) Web Services from existing Java code a breeze, this is exactly the approach you should not follow, if maintenance, extensibility and flexibility is of concern.
So, here is the first drawback: we will use the slightly more complicated “contract-first” approach. But just believe in me: after having dealt with several Web Services, you will never regret the challenging beginning when it comes to maintenance of existing services (including versioning, implementing fixes/patches, etc.)
Let’s go
During this post, the Spring Tool Suite (currently 2.6.0) is used. You can just as well use Eclipse (or any other IDE), the STS is used since the Maven plugin is already included, as well as Spring tooling, which supports Spring Web Services and eases usage of this framework.
- Download SpringSource Tool Suite
- Create new Maven Project with spring-ws-archetype
- Add XML Schema (the contract)
- Generate JAXB classes, the XML-Java data binding from the XML Schema files
- Create Endpoint and Spring WS annotations and config
- …that’s it – test it
Step 2: Create new Maven Project with spring-ws-archetype
Choose “File / New / Other… / Maven Project”
Choose “archetype selection” (do not check skip…)
Choose “Add archetype” and type values according to screenshot (we need to add at least version 2.0.2, currently only 2.0.0-M1 is available in catalog)
Choose the added archetype from selection
Choose the settings for your project, these values are arbitrary (for testing purposes you can use the ones from the screenshot)
The steps above can be shortened by just creating the Maven project from the command line and importing the generated project as an existing maven project in STS/Eclipse (with “File / Import / Existing Maven project”).
mvn archetype:create -DarchetypeGroupId=org.springframework.ws -DarchetypeArtifactId=spring-ws-archetype -DarchetypeVersion=2.0.2.RELEASE -DgroupId=de.ice09.blog -DartifactId=userservice-ws
However, for this to work Maven has to be installed correctly, M2_HOME and JAVA_HOME have to be set correctly, etc. Using the steps above just require STS or Eclipse + Maven plugin to be installed.
More details about these steps are available in this short & nice blog post.
Right now this project structure should have been generated
Step 3: Add XML Schema (the contract)
There are several ways for creating a XML Schema, you can use XMLSpy, which to me is the best tool for this task, but is quite expensive. The XML Schema editor in Eclipse is quite useful as well (and integrated and free).
A recent discovery is WMHelp XMLPad, which is pretty cool and supports diagrams similar to those of XMLSpy:
These diagrams are quite self-explanatory, we want to have to services, the first one accepts requests of type GetUserRequest, which just have an id and returns a GetUserResponse, which consists of id, firstname, lastname, and 1..n buddies, which themselves are just ids. The second service is StoreUser, which is the “storage” equivalent if the “reading” GetUser service.
Since the contract is the most important part of the Web Service, I will list the XML Schemas as well as sample data here, you can just skip them if you are more interested in the framework usage.
Please note: all posted source code in this blog post is just for clarifying purpose, you do not need to copy & paste this code – a complete SpringSource Tool Suite/Maven-project is available on Google Code.
XML Schema (just GetUser, StoreUser is similar)
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://de.ice09.blog/userservice-ws/"
targetNamespace="http://de.ice09.blog/userservice-ws/">
<element name="GetUserRequest">
<complexType>
<sequence>
<element name="id" type="string"/>
</sequence>
</complexType>
</element>
<element name="GetUserResponse">
<complexType>
<sequence>
<element name="id" type="string"/>
<element name="firstname" type="string"/>
<element name="lastname" type="string"/>
<sequence>
<element name="Buddies">
<complexType>
<sequence>
<element name="Buddy" type="string" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
</element>
</sequence>
</sequence>
</complexType>
</element>
</schema>
Step 4: Generate JAXB classes, the XML-Java data binding from the XML Schema files
For the next step to work we have to enrich our Maven pom.xml with a jaxb-maven-plugin. This plugin will create the JAXB-classes from the XML Schema files during a Maven build.
Add the following lines to the pom.xml in the project’s root folder in section “plugins” as well as the dependencies in section “dependencies”. After saving the file, a new folder “generated-sources” in the target-folder should be created (if this does not happen, choose “Project / Clean”). These are the generated classes, which have to be added to the sources.
JAXB2-Maven-Plugin
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<schemaDirectory>src/main/webapp/WEB-INF/</schemaDirectory>
</configuration>
</plugin>
JAXB2-Maven-Dependencies
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>compile</scope>
<version>1.2.16</version>
</dependency>
Step 5: Create Endpoint and Spring WS annotations and config
Create a folder named “java” in src/main-folder and add this folder to the source path, analog to the generated-sources folder. This step might not be necessary in future archetype versions.
Afterwards, this classes must be added to the source folder.
//[imports removed]
@Endpoint
public class GetUserEndpoint {
@Autowired
private UserService userService;
@PayloadRoot(localPart = "GetUserRequest", namespace = "http://de.ice09.blog/userservice-ws/")
@ResponsePayload
public GetUserResponse getUser(@RequestPayload GetUserRequest request) {
GetUserResponse response = new ObjectFactory().createGetUserResponse();
response.setLastname(userService.getName());
response.setFirstname("firstname");
return response;
}
}
A detailed explanation of the annotation can be found on the Spring Web Services site. For now, only a minimal explanation is given: The @Endpoint annotation lets Spring know that this class will be used as a Web Service endpoint. The @Autowired annotation is standard Spring and results in the service class being injected to the endpoint automatically without XML declarations. The interesting part is the @PayloadRoot annotation in combination with @RequestPayload and @ResponsePayload. There is a lot of convention-over-configuration going on here and a lot of magic inside. If used this way, Spring recognizes the generated JAXB-classes on the classpath. When a XML request with an XML element “GetUserRequest” (localPart) and namespace “http://de.ice09.blog/userservice-ws” arrives, the generated JAXB-classes are used to marshall the XML element to a Java object due to the @Request/ResponsePayload annotations and the presence of JAXB and the generated JAXB-classes on the classpath.
If this is new to you, you should get familiar with it, otherwise it will fall back on you if you have to change anything, eg. from JAXB to Castor, DOM, XPath or whatever. This described usage (JAXB and annotations) is the easiest way possible, but the framework allows for almost any thinkable combination and framework usage. There might be more configuration necessary though.
The service is just listed for completeness, it does nothing special. Just note the @Coomponent annotation. This lets Spring know that this bean/class will be injected somewhere, eg. by using @Autowired as we did.
de.ice09.blog.ws.service;
import org.springframework.stereotype.Component;
@Component
public class UserService {
public String getName() {
return "lastname";
}
}
Now for the most interesting component, the Spring config. It is already present in the WEB-INF directory. You just have to insert these XML elements:
<?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:sws="http://www.springframework.org/schema/web-services"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/web-services http://www.springframework.org/schema/web-services/web-services-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
<property name="soapVersion">
<util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12" />
</property>
</bean>
<sws:annotation-driven />
<context:component-scan base-package="de.ice09.blog.ws" />
<sws:dynamic-wsdl id="GetUser" portTypeName="UserManagement"
createSoap12Binding="true" createSoap11Binding="false" locationUri="/userservice"
targetNamespace="http://de.ice09.blog/userservice-ws/">
<sws:xsd location="/WEB-INF/GetUser.xsd" />
</sws:dynamic-wsdl>
<sws:dynamic-wsdl id="StoreUser" portTypeName="UserManagement"
createSoap12Binding="true" createSoap11Binding="false" locationUri="/userservice"
targetNamespace="http://de.ice09.blog/userservice-ws/">
<sws:xsd location="/WEB-INF/StoreUser.xsd" />
</sws:dynamic-wsdl>
</beans>
The most important element is <sws:annotation-driven>, together with <context:componant-scan>. These elements do all the magic, I highly advise you to get accustomed to the way these elements work. The other parts are just for automatic WSDL generation, which eases the usage of the generated Web Services a lot, but are not necessary for the services themselves. The messageFactory is just for compliance with SOAP 1.2 and can be ignored – just make sure you include it if you want to use SOAP 1.2 instead of SOAP 1.1. Same is true for the dynamic WSDL-generation – the attributes must be set to true and false respectively.
For the WSDL generation, the web.xml has to be modified as well, just add the init-param lines:
<servlet>
<servlet-name>spring-ws</servlet-name>
<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet
</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
Step 6: …that’s it – test it
Yes, that’s it. We created a complete new Web Service with really simple steps.
Just start the Web application now on the STS included tc-Server, just by right-clicking the project and choosing “Run As… / Run on Server”
Afterwards, navigate to http://localhost:8080/userservicews/GetUser.wsdl there should be a WSDL printed in the browser.
We can now test the Web Service with a Web Service client, for now we will use soapUI, which is a pretty neat tool for this purpose. You can Web Start it or download it, it requires no install. Even better than soapUI is the Spring Web Service client, which we will investigate in the following blog entry.
After starting soapUI, create a new project and provide the aforementioned WSDL-Url. soapUI will ask for creating a test suite and test cases, which should just be unmodified accepted.
If everything worked, you can just choose the created test suite with its test case and run the test case. The result should look like the screenshot.
If this blog post was useful to you, you can tip me. Maybe you do not want to tip, but please inform yourself about Bitcoin and applications of Bitcoin like Youttipit.