As I have posted before we are using XFire to implement our web service. Unfortunately, we are running into some serious issues with the framework, so we decided to look after another implementation. Since XFire is no longer supported by the community (or at least at a very low profile) and ‘replaced’ by CXF, we decided to have a look at Spring WS. We also have used the Spring Framework throughout our application elsewhere, so this would be a logical choice. After a quick look at the framework it convinced us that this would be the framework to use for our web services. It was easy to set up and well documented.
In this post I will show you how to use StAX to parse and write the SOAP request and response in combination with Spring WS, Maven2 and JBoss.
- Setup maven
- Create a Maven2 web project
- Create the XML Schema’s (XSD) for the request and response SOAP messages
- Configure the web application
- Create model classes
- Create the StAX parser and writer classes
- Implement endpoint/webservice
- Setup the SpringWS context
- Build and deploy to jboss
- Access wsdl in browser to test
First thing to do is to setup Maven2. I assume this won’t be a problem, so I only show the settings.xml which I use for my Maven2 installation (you can also check out this post and this one):
<settings> <localRepository>d:/maven2/repo/</localRepository> <profiles> <profile> <id>default-repositories</id> <repositories> <repository> <id>global</id> <name>External Mirror of Central Repository</name> <url>http://repo1.maven.org/maven2</url> </repository> <repository> <id>repo.maven</id> <url>http://repo1.maven.org/maven2-repoclean-java.net</url> </repository> <repository> <id>jboss.maven</id> <url>http://repository.jboss.com/maven2/</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>repo1org</id> <name>External Plugin Repository</name> <url>http://repo1.maven.org/maven2/</url> </pluginRepository> </pluginRepositories> </profile> <profile> <id>dev-environment</id> <activation> <activeByDefault>true</activeByDefault> <property> <name>env</name> <value>dev</value> </property> </activation> <properties> <appserver.home>d:/java/jboss-4.0.5</appserver.home> <server.name>default</server.name> </properties> </profile> </profiles> <activeProfiles> <activeProfile>dev-environment</activeProfile> <activeProfile>default-repositories</activeProfile> </activeProfiles> </settings>
You can do this with this command (as described in more detail here):
mvn archetype:create -DgroupId=net.pascalalma -DartifactId=spring-ws-test
-DarchetypeArtifactId=maven-archetype-webapp
As an example I take the schema of the Spring WS reference documentation. The request is described there and I have added a response myself. Here is the xsd:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:hr="http://www.pascalalma.net/hr/schemas" elementFormDefault="qualified" targetNamespace="http://www.pascalalma.net/hr/schemas"> <xs:element name="HolidayRequest"> <xs:complexType> <xs:all> <xs:element name="Holiday" type="hr:HolidayType"/> <xs:element name="Employee" type="hr:EmployeeType"/> </xs:all> </xs:complexType> </xs:element> <xs:complexType name="HolidayType"> <xs:sequence> <xs:element name="StartDate" type="xs:date"/> <xs:element name="EndDate" type="xs:date"/> </xs:sequence> </xs:complexType> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="Number" type="xs:integer"/> <xs:element name="FirstName" type="xs:string"/> <xs:element name="LastName" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:element name="HolidayResponse" type="hr:Status" /> <xs:simpleType name="Status"> <xs:restriction base="xs:string"> <xs:enumeration value="DENIED"/> <xs:enumeration value="PENDING"/> <xs:enumeration value="APPROVED"/> </xs:restriction> </xs:simpleType> </xs:schema>
I have stored this schema as ‘hr.xsd’ in the directory ‘$PROJECT_HOME$/WEB-INF/schema’.
We have to reroot the incoming HTTP request to the Spring WS servlet. TO do this we define the Spring WS servlet in the web.xml, like this:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <!-- === Servlets === --> <servlet> <servlet-name>spring-ws</servlet-name> <display-name>Spring-WS Servlet</display-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> <servlet-mapping> <servlet-name>spring-ws</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> <welcome-file-list id="WelcomeFileList"> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
Now it’s time to start some Java coding. First I create the model classes that will represent the Objects as described by the xsd. We will have the following classes:
The Employee.java
The Holiday.java
And the HolidayRequest.java
To get from the incoming XML to our Java model classes we make use of StAX parsing and writing. Here are the two classes that do the XML-Object translation and back:
HolidayRequestReader.java
HolidayResponseWriter.java
The last Java class we have to make is the Webservice implementation itself. Since we want to use StAX parsing, I simply extend the Spring class ‘AbstractStaxStreamPayloadEndpoint’:
package net.pascalalma.ws; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import net.pascalalma.model.HolidayRequest; import net.pascalalma.xml.HolidayRequestReader; import net.pascalalma.xml.HolidayResponseWriter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.ws.server.endpoint.AbstractStaxStreamPayloadEndpoint; public class HolidayRequestService extends AbstractStaxStreamPayloadEndpoint { private static final Log LOG = LogFactory.getLog(HolidayRequestService.class); @Override protected void invokeInternal(XMLStreamReader streamreader, XMLStreamWriter streamwriter) throws Exception { try { HolidayRequest hr = HolidayRequestReader.parseMessage(streamreader); LOG.info("Result = " + hr); HolidayResponseWriter.writeResponse(hr, streamwriter); } catch (Throwable t) { LOG.error("Catched exception", t); } } }
Now that we have all the Java classes in place we have to configure the Spring context that is used by our webservice implementation. In our case it looks like the following XML file that is stored under the name ‘spring-ws-servlet’ in the ‘$PROJECT_HOME$/webapp/WEB-INF’ directory:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootQNameEndpointMapping"> <property name="mappings"> <props> <prop key="{http://www.pascalalma.net/hr/schemas}HolidayRequest"> myEndPoint </prop> </props> </property> </bean> <bean id="myEndPoint" class="net.pascalalma.ws.HolidayRequestService" > </bean> <bean id="holiday" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition"> <property name="schema" ref="schema" /> <property name="portTypeName" value="HumanResource" /> <property name="locationUri" value="http://localhost:8080/spring-ws-test/services/holidayService/" /> <property name="targetNamespace" value="http://www.pascalalma.net/hr/definitions" /> </bean> <bean id="schema" class="org.springframework.xml.xsd.SimpleXsdSchema"> <property name="xsd" value="/WEB-INF/schemas/hr.xsd" /> </bean> </beans>
With this file as the project’s POM file we can simply build, package and deploy our webservice to JBoss with the command in the command prompt:
mvn clean package jboss:harddeploy
If everything went well and JBoss is started, you can access the dynamically generated WSDL with the following url:
http://localhost:8080/spring-ws-test/services/holiday.wsdl
This should give the following WSDL in your browser:
<wsdl:definitions targetNamespace="http://www.pascalalma.net/hr/definitions"> <wsdl:types> <xs:schema elementFormDefault="qualified" targetNamespace="http://www.pascalalma.net/hr/schemas"> <xs:element name="HolidayRequest"> <xs:complexType> <xs:all> <xs:element name="Holiday" type="hr:HolidayType"/> <xs:element name="Employee" type="hr:EmployeeType"/> </xs:all> </xs:complexType> </xs:element> <xs:complexType name="HolidayType"> <xs:sequence> <xs:element name="StartDate" type="xs:date"/> <xs:element name="EndDate" type="xs:date"/> </xs:sequence> </xs:complexType> <xs:complexType name="EmployeeType"> <xs:sequence> <xs:element name="Number" type="xs:integer"/> <xs:element name="FirstName" type="xs:string"/> <xs:element name="LastName" type="xs:string"/> </xs:sequence> </xs:complexType> <xs:element name="HolidayResponse" type="hr:Status"/> <xs:simpleType name="Status"> <xs:restriction base="xs:string"> <xs:enumeration value="DENIED"/> <xs:enumeration value="PENDING"/> <xs:enumeration value="APPROVED"/> </xs:restriction> </xs:simpleType> </xs:schema> </wsdl:types> <wsdl:message name="HolidayResponse"> <wsdl:part element="sch:HolidayResponse" name="HolidayResponse"> </wsdl:part> </wsdl:message> <wsdl:message name="HolidayRequest"> <wsdl:part element="sch:HolidayRequest" name="HolidayRequest"> </wsdl:part> </wsdl:message> <wsdl:portType name="HumanResource"> <wsdl:operation name="Holiday"> <wsdl:input message="tns:HolidayRequest" name="HolidayRequest"> </wsdl:input> <wsdl:output message="tns:HolidayResponse" name="HolidayResponse"> </wsdl:output> </wsdl:operation> </wsdl:portType> <wsdl:binding name="HumanResourceSoap11" type="tns:HumanResource"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/> <wsdl:operation name="Holiday"> <soap:operation soapAction=""/> <wsdl:input name="HolidayRequest"> <soap:body use="literal"/> </wsdl:input> <wsdl:output name="HolidayResponse"> <soap:body use="literal"/> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="HumanResourceService"> <wsdl:port binding="tns:HumanResourceSoap11" name="HumanResourceSoap11"> <soap:address location="http://localhost:8080/spring-ws-test/services/holidayService/"/> </wsdl:port> </wsdl:service> </wsdl:definitions>
That’s it. You can test your web service with a tool like SoapUI and create test cases based on the generated WSDL like I described here.
Hey,
This is a wonderful post yar… Thanks a lot. You saved a lot of time.
BTW i saw a small problem in port binding
why the name is HumanResourceSoap11 ? how can i remove the 11 from name?
regards,
Vipin
You can just leave out the 11 in the wsdl:binding, but make sure you do the same in wsdl:port binding so the reffering name matches.
Hi,
This is a wonderful post, it was very useful.
Please can you give me an example to parse the entire soap envelope using StAX? I should access the soap headers included in the soap envelope using StAX for my requirement.
I found that invokeInternal(XMLStreamReader streamreader,XMLStreamWriter streamwriter)method provides access only to payload (i.e content of the soap body) not to the entire soap envelope.
Regards,
K. Harish
harish.gtec@gmail.com
Hi,
I don’t have an example for the entire soap envelope. See the documentation here, which explains when you can access only the payload and when the complete message.
I hope this helps.