Java and Spring development

Posts Tagged ‘XSD

Contract first web service with Spring WS

with 8 comments

Introduction

This tutorial demonstrates how to develop a web service with Spring WS. It’s a contract first web service that uses JAXB for the binding. The focus area is how to configure and use Spring WS. Details about how to create an XSD schema and JAXB classes are explained in this tutorial.

I will also briefly show exception handling and validation support in relation to SOAP faults. But first some words about the contract first part.

Generate an XSD schema and JAXB classes

The sample XML request document that I will create an XSD schema from:

<accountBalanceRequest number="123456789" name="Bob" />

And the corresponding response document:

<accountBalanceResponse balance="100.50" number="1234567890" time="2009-05-30T09:30:10.5" />

After some tweaking, the XSD schema looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified"
	targetNamespace="http://www.redpill-linpro.com/account-balance-service"
	xmlns:a="http://www.redpill-linpro.com/account-balance-service">

	<xs:element name="accountBalanceRequest">
		<xs:complexType>
			<xs:attribute name="name" use="required" type="xs:string" />
			<xs:attribute name="number" use="required">
				<xs:simpleType>
					<xs:restriction base="xs:string">
						<xs:pattern value="\d{10}" />
					</xs:restriction>
				</xs:simpleType>
			</xs:attribute>
		</xs:complexType>
	</xs:element>

	<xs:element name="accountBalanceResponse">
		<xs:complexType>
			<xs:attribute name="balance" use="required" type="xs:decimal" />
			dateTime" />
		</xs:complexType>
	</xs:element>

</xs:schema>

After I have generated the JAXB classes, the source folder containing generated classes looks like this:

Now I have created the data contract part of the WSDL and generated the data binding classes used to parse the Java object to XML. Then its time to understand how Spring WS works.

The basics with Spring WS

You don’t need to create a WSDL file with Spring WS. The WSDL file is generated from one or more XSD schemas and some few Spring configuration beans. Spring WS solves the rest with naming patterns.

The request element must end with Request and the response element must end with Response. The same applies to Fault elements. The WSDL operation will be the element names except the Request/Response part. Here is the generated WSDL operation:

<wsdl:operation name="accountBalance">
 <wsdl:input message="tns:accountBalanceRequest" name="accountBalanceRequest">
 </wsdl:input>
 <wsdl:output message="tns:accountBalanceResponse" name="accountBalanceResponse">
 </wsdl:output>
</wsdl:operation>

The server code isn’t aware of the WSDL. This is a different approach than for example CXF that generates an interface your implementation must implement. The server implementation only has dependencies to the JAXB generated request and response classes. Spring WS finds the implementing method of a WSDL operation with annotations on the service class.

Server implementation

This tutorial contains one stub implementation. The implementation returns the same response every time except if the account number starts with 9. Then it throws a runtime exception. More about exception handling later. The implementation class must have the Spring @Endpoint annotation above the class name and the @PayloadRoot annotation above the method that will handle the web service request. The @PayloadRoot annotation must know the XSD schema namespace and the name of the request element specified in the XSD schema. See the highlighted lines in this stub implementation:

@Endpoint
public class AccountBalanceServiceStub {

	@PayloadRoot(namespace="http://www.redpill-linpro.com/account-balance-service",
			localPart="accountBalanceRequest")
	public AccountBalanceResponse findAccountBalance(
			AccountBalanceRequest accountBalanceRequest) {
		final AccountBalanceResponse response;

		if(isRequestValid(accountBalanceRequest)) {
			response = createDummyResponse();
		} else {
			throw new AccountNumberNotFoundException(
					"Account number " + accountBalanceRequest.getNumber() + " not valid.");
		}

		return response;
	}

	private AccountBalanceResponse createDummyResponse() {
		AccountBalanceResponse response = new ObjectFactory()
			.createAccountBalanceResponse();

		response.setBalance(BigDecimal.valueOf(100.50));
		response.setTime(XMLGregorianCalendarImpl.createDateTime(2010, 4, 4, 12, 0, 0));
		return response;
	}

	private boolean isRequestValid(final AccountBalanceRequest accountBalanceRequest) {
		final boolean valid;

		if(accountBalanceRequest.getNumber().startsWith("9")) {
			valid = false;
		} else {
			valid = true;
		}

		return valid;
	}
}

The @Endpoint annotation is a specialization of the @Component annotation. All beans marked with @Component can be found with a componet scan element in the Spring configuration.

<context:component-scan base-package="com.redpill.linpro" />

Another alternative for those that prefer to define their Spring beans explicitly:

<bean class="com.redpill.linpro.transport.account.ws.AccountBalanceServiceStub" />

Spring WS needs an implementation of the EndpointMapping interface like this:

<bean class="org.springframework.ws.server.endpoint.mapping
	.PayloadRootAnnotationMethodEndpointMapping" />

Since the stub implementation uses JAXB classes as input parameter and return type, the Spring configuration must also include these two beans for parsing XML:

<oxm:jaxb2-marshaller id="marshaller"
	contextPath="com.redpill.linpro.ws.account.balance.service" />

<bean class="org.springframework.ws.server.endpoint.adapter
	.GenericMarshallingMethodEndpointAdapter">

	<constructor-arg ref="marshaller" />
</bean>

Exception handling and SoapFault

If something unexpected occurs on the server, it’s best practice with modern programming languages to throw exceptions. The SOAP specification supports SOAP faults and with a framework like Spring WS, these SOAP faults are converted into runtime exceptions of type SoapFaultException. An example of an SOAP fault captured with an TCP/IP monitor:

<SOAP-ENV:Body>
	<SOAP-ENV:Fault>
		<faultcode>SOAP-ENV:Client</faultcode>
		<faultstring xml:lang="en">Account number 9998765432 not valid.
	</SOAP-ENV:Fault>
</SOAP-ENV:Body>

The SOAP fault above is the result of throwing an AccountNotFoundException.

It’s several ways to configure Spring WS to convert an exception into a SOAP fault element, but I will only demonstrate how to do it with annotations. If you’re interested, see the reference documentation for more information about the mapping alternative.

The @SoapFault annotation below helps the exception resolver to convert the exception to a SoapFault. Ant the FaultCode enum will be mapped to a fault-code element.

@SoapFault(faultCode = FaultCode.CLIENT)
public class AccountNumberNotFoundException extends RuntimeException {

	public AccountNumberNotFoundException(String message) {
		super(message);
	}
}

The exception resolver bean that converts all the exceptions marked with @SoapFault annotation:

<bean class="org.springframework.ws.soap.server.endpoint
	.SoapFaultAnnotationExceptionResolver" />

A good web service client framework should convert a SOAP fault element to an exception and throw it to the client as a runtime exception. Below is a stacktrace printed from a caught SoapFaultClientException on the client-side:

org.springframework.ws.soap.client.SoapFaultClientException: Account number 9998765432 not valid.
	at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:37)
	at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:738)
	at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:564)
	at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:502)
	at org.springframework.ws.client.core.WebServiceTemplate.marshalSendAndReceive(WebServiceTemplate.java:351)
       ....

The SOAP specification supports five fault-codes and it’s easy to configure in the @SoapFault annotation.

Validating

To enable validating, you have to add a PayloadValidatingInterceptor and reference to that interceptor from the interceptor list in the PayloadRootAnnotationMethodEndpointMapping.

<bean
	class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
	<property name="interceptors">
		<list>
			<ref local="validatingInterceptor" />
		</list>
	</property>
</bean>

<bean
	class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver" />

<bean id="validatingInterceptor"
	class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
	<property name="schema" value="/WEB-INF/schemas/account-balance-service.xsd" />
	<property name="validateRequest" value="true" />
	<property name="validateResponse" value="true" />
</bean>

If you’re not specifying the validate parameters above, only the request will be validated by default.

An example of a SOAP fault returned from the server with a request containing an account number with more than ten digits:

<SOAP-ENV:Fault>
	<faultcode>SOAP-ENV:Client</faultcode>
	<faultstring xml:lang="en">Validation error</faultstring>
	<detail>
		ValidationError xmlns:spring-ws="http://springframework.org/spring-ws">
                        cvc-pattern-valid:
			Value '0987654321000000000000000000000000000' is not facet-valid with
			respect to pattern '\d{10}' for type
			'#AnonType_numberaccountBalanceRequest'.
			</spring-ws:ValidationError>
		<spring-ws:ValidationError xmlns:spring-ws="http://springframework.org/spring-ws">
                        cvc-attribute.3:
			The value '0987654321000000000000000000000000000' of attribute 'number' on
			element 'ns2:accountBalanceRequest' is not valid with respect to
			its type, 'null'.</spring-ws:ValidationError>
	</detail>
</SOAP-ENV:Fault>

As you can see, validating doesn’t require any code changes. It’s just to add an interceptor in the configuration. The same can be done in other areas like security.

WSDL specific configuration

Spring WS generates the WSDL document on runtime. Since the generating is based on naming patterns as described in the basic chapter earlier in this tutorial, all you need to specify is the schema, the name of the port type and the URI. You can specify a full URI, but since the dispatcher servlet already knows the application’s URI, it’s sufficient to only add the relative URI.

The WSDL definition Spring bean used in this tutorial:

<bean name="accountBalanceServiceDefinition"
	class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
	<property name="schema">
		<bean class="org.springframework.xml.xsd.SimpleXsdSchema">
			<property name="xsd"
				value="/WEB-INF/schemas/account-balance-service.xsd" />
		</bean>
	</property>
	<property name="portTypeName" value="AccountBalanceService" />
	<property name="locationUri" value="/account-balance-service" />
</bean>

The WSDL file this bean generates is available if you click on the link below:

<wsdl:definitions
	targetNamespace="http://www.redpill-linpro.com/account-balance-service">

	<wsdl:types>
		<xs:schema elementFormDefault="qualified"
			targetNamespace="http://www.redpill-linpro.com/account-balance-service">

			<xs:element name="accountBalanceRequest">
				<xs:complexType>
					<xs:attribute name="name" type="xs:string" use="required" />
					<xs:attribute name="number" use="required">
						<xs:simpleType>
							<xs:restriction base="xs:string">
								<xs:pattern value="\d{10}" />
							</xs:restriction>
						</xs:simpleType>
					</xs:attribute>
				</xs:complexType>
			</xs:element>

			<xs:element name="accountBalanceResponse">
				<xs:complexType>
					<xs:attribute name="balance" type="xs:decimal" use="required" />
					<xs:attribute name="time" type="xs:dateTime" use="required" />
				</xs:complexType>
			</xs:element>
		</xs:schema>
	</wsdl:types>

	<wsdl:message name="accountBalanceRequest">
		<wsdl:part element="tns:accountBalanceRequest" name="accountBalanceRequest">
		</wsdl:part>
	</wsdl:message>

	<wsdl:message name="accountBalanceResponse">
		<wsdl:part element="tns:accountBalanceResponse" name="accountBalanceResponse">
		</wsdl:part>
	</wsdl:message>

	portType name="AccountBalanceService">
		<wsdl:operation name="accountBalance">
			<wsdl:input message="tns:accountBalanceRequest" name="accountBalanceRequest">
			</wsdl:input>
			<wsdl:output message="tns:accountBalanceResponse" name="accountBalanceResponse">
			</wsdl:output>
		</wsdl:operation>
	</wsdl:portType>

	AccountBalanceServiceSoap11" type="tns:AccountBalanceService">
		<soap:binding style="document"
			transport="http://schemas.xmlsoap.org/soap/http" />

		<wsdl:operation name="accountBalance">
			soapAction="" />
			<wsdl:input name="accountBalanceRequest">
				<soap:body use="literal" />
			</wsdl:input>
			<wsdl:output name="accountBalanceResponse">
				<soap:body use="literal" />
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<wsdl:service name="AccountBalanceServiceService">
		<wsdl:port binding="tns:AccountBalanceServiceSoap11" name="AccountBalanceServiceSoap11">
			<soap:address location="/account-balance-service" />
		</wsdl:port>
	</wsdl:service>
</wsdl:definitions>

The URL to the WSDL file can be found with this pattern: URL to the servlet + DefaultWsdl11Definition bean name + .wsdl. In my case it’s available here: http://localhost:8081/ws-demo/account-balance-service/accountBalanceServiceDefinition.wsdl

Servlet configuration

The web.xml file must configure the Spring WS servlet to be of type MessageDispatcherServlet.

<servlet-name>account-balance-service</servlet-name>
	<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/spring/account-balance-service-ws-config.xml</param-value>
	</init-param>
</servlet>

This servlet has its own Spring context with all the web service logic.

It’s a good practice to separate the servlet specific Spring configuration from the business and integration logic. Since this tutorial only contains a stub implementation, this application doesn’t have any business layer. With a normal application, the web.xml file should also contain these two elements:

<pre><context-param>
	<param-name>contextConfigLocation</param-name>
	classpath:config.xml
</context-param>

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

The config.xml file in this tutorial is just a dummy file with none Spring beans. If you have many separate Spring configuration files, then you can add them all in the param-value element separated with a comma.

Finally, you have to add mapping elements before the dispatcher servlet will work as expected.

<servlet-mapping>
	<servlet-name>account-balance-service</servlet-name>
	<url-pattern>/account-balance-service/*</url-pattern>
</servlet-mapping>

<mime-mapping>
	<extension>wsdl</extension>
	<mime-type>text/xml</mime-type>
</mime-mapping>

<mime-mapping>
	<extension>xsd</extension>
	<mime-type>text/xml</mime-type>
</mime-mapping>

Click on the show source link below if you want to see the whole web.xml file.

<?xml version="1.0" encoding="ISO-8859-1"?>
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:config.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<servlet>
		<servlet-name>account-balance-service</servlet-name>
		<servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/account-balance-service-ws-config.xml</param-value>
		</init-param>
	</servlet>

	<servlet-mapping>
		<servlet-name>account-balance-service</servlet-name>
		<url-pattern>/account-balance-service/*</url-pattern>
	</servlet-mapping>

	<mime-mapping>
		<extension>wsdl</extension>
		<mime-type>text/xml</mime-type>
	</mime-mapping>

	<mime-mapping>
		<extension>xsd</extension>
		<mime-type>text/xml</mime-type>
	</mime-mapping>

</web-app>

The important beans in the Spring servlet configuration file is already described earlier in this tutorial, but the link below shows all the beans.

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

	<oxm:jaxb2-marshaller id="marshaller"
		contextPath="com.redpill.linpro.ws.account.balance.service" />

	<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
		<constructor-arg ref="marshaller" />
	</bean>

	<bean
		class="org.springframework.ws.server.endpoint.mapping
		.PayloadRootAnnotationMethodEndpointMapping">
		<property name="interceptors">
			<list>
				<ref local="validatingInterceptor" />
			</list>
		</property>
	</bean>

	<bean class="org.springframework.ws.soap.server.endpoint.SoapFaultAnnotationExceptionResolver" />

	<bean id="validatingInterceptor"
		class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
		<property name="schema" value="/WEB-INF/schemas/account-balance-service.xsd" />
		<property name="validateRequest" value="true" />
		<property name="validateResponse" value="true" />
	</bean>

	<bean name="accountBalanceServiceDefinition"
		class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
		<property name="schema">
			<bean class="org.springframework.xml.xsd.SimpleXsdSchema">
				<property name="xsd"
					value="/WEB-INF/schemas/account-balance-service.xsd" />
			</bean>
		</property>
		<property name="portTypeName" value="AccountBalanceService" />
		<property name="locationUri" value="/account-balance-service" />
	</bean>

	<context:component-scan base-package="com.redpill.linpro" />

</beans>

Summary

Spring WS is a lightweight web service alternative that takes care of the WSDL for you. It’s the easiest framework I have tried and should be a good framework in most situations. If you like to generate the service interfaces in addition to the transfer objects, you should consider CXF that’s another great alternative, else give this one a chance.

I have never been a huge fan of WSDL files and an alternative that handles all the plumbing for me is very nice. That enables faster development without loosing control over the data being exhanged with the client.

The code used in this article was developed in my work time at Redpill Linpro. It can be downloaded here


Redpill Linpro is the leading provider of Professional Open Source services and products in the Nordic region.

Advertisements

Written by Espen

February 28, 2010 at 23:55

Posted in Web Services

Tagged with , , ,

Spring WS client

with 4 comments

Introduction

The Spring WS client is a lightweight alternative that doesn’t need a WSDL to work. You code against a template like Spring’s other templates for communicating against a database or JMS server. This blog article demonstrates how to use Spring WS as a client with JAXB for the data binding and how to add pre and post processing behaviour with interceptors.

If you’re not familiar with generating JAXB classes, then take a look at this tutorial.

How to use the WebServiceTemplate class

Since Spring WS doesn’t use a service contract, you must know the request and response type. The test below demonstrates how to create and instantiate a request object of a JAXB generated class, call the marshallSendAndReceive method with it and how to cast the response object to an object of the JAXB generated response class. Finally, the test asserts that the service returned the expected result.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("test-config.xml")
public class AccountBalanceServiceTest {

	@Autowired
	private WebServiceTemplate webServiceTemplate;

	@Test
	public void testAccountBalanceService() {

		AccountBalanceRequest request = new ObjectFactory()
				.createAccountBalanceRequest();
		request.setName("Willy");
		request.setNumber("0987654321");
		AccountBalanceResponse response = (AccountBalanceResponse) webServiceTemplate
				.marshalSendAndReceive(request);

		assertEquals(BigDecimal.valueOf(100.5), response.getBalance());
	}
}

As you see, none web service specific information. All the web service details are hiding in the configuration.

Spring configuration

The test above requires a marshaller and WebServiceTemplate bean. The marshaller must specify the context path. That’s the package name for the generated JAXB classes.

<oxm:jaxb2-marshaller id="marshaller"
	contextPath="com.redpill.linpro.ws.account.balance.service" />

The template bean must specify the JAXB marshaller and the URI to the web service’s servlet URI.

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">

	<property name="marshaller" ref="marshaller" />
	<property name="unmarshaller" ref="marshaller" />
	<property name="defaultUri"
		value="http://localhost:8081/ws-demo/account-balance-service" />
</bean>

Retrieving standard SOAP faults

When the SOAP response contains a SOAP fault, then Spring WS converts it into a SoapFaultClientException. This test calls a web service and expects an exception back:

@Test(expected = SoapFaultClientException.class)
public void testAccountBalanceServiceWithTooLongAccountNumber() {

	AccountBalanceRequest request = new AccountBalanceRequest();
	request.setName("Willy");
	request.setNumber("0987654321000000000000000000000000000");

	webServiceTemplate.marshalSendAndReceive(request);
}

It’s caught by the JUnit test in the same way as you can catch every Java exceptions.

Validating

The client part of Spring WS can validate the parsed XML before it sends the XML document. You only need to specify a validator interceptor in the configuration and reference to it from the WebServiceTemplate bean.

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
	<property name="marshaller" ref="marshaller" />
	<property name="unmarshaller" ref="marshaller" />
	<property name="defaultUri"
		value="http://localhost:8081/ws-demo/account-balance-service" />
	<property name="interceptors">
		<list>
			<ref bean="payloadValidatingInterceptor" />
		</list>
	</property>
</bean>

<bean id="payloadValidatingInterceptor"
	class="org.springframework.ws.client.support.interceptor.PayloadValidatingInterceptor">
	<property name="schema"
		value="file:WebContent/WEB-INF/schemas/account-balance-service.xsd" />
	<property name="validateRequest" value="true" />
	<property name="validateResponse" value="true" />
</bean>

This configuration should have worked. But it doesn’t work on my machine with Spring WS 1.5.9.A. A snippet from the odd log message:

<XML validation error on request: cvc-complex-type.3.2.2: Attribute 'name' is not allowed to appear in element 'ns2:accountBalanceRequest'.>
<XML validation error on request: cvc-complex-type.4: Attribute 'name' must appear on element 'ns2:accountBalanceRequest'.>

You can click the link to see the whole log message.

ERROR [org.springframework.ws.client.support.interceptor.PayloadValidatingInterceptor] - <XML validation error on request: cvc-complex-type.3.2.2: Attribute 'name' is not allowed to appear in element 'ns2:accountBalanceRequest'.>
ERROR [org.springframework.ws.client.support.interceptor.PayloadValidatingInterceptor] - <XML validation error on request: cvc-complex-type.3.2.2: Attribute 'number' is not allowed to appear in element 'ns2:accountBalanceRequest'.>
ERROR [org.springframework.ws.client.support.interceptor.PayloadValidatingInterceptor] - <XML validation error on request: cvc-complex-type.4: Attribute 'name' must appear on element 'ns2:accountBalanceRequest'.>
ERROR [org.springframework.ws.client.support.interceptor.PayloadValidatingInterceptor] - <XML validation error on request: cvc-complex-type.4: Attribute 'number' must appear on element 'ns2:accountBalanceRequest'.>

It’s an issue with the SAX validator and the DOM source element that represents the parsed XML. A quick fix to solve this problem is to transform the DOM source to a String. Create a StringReader with the String and create a StreamSource object with the StringReader. For all the details, see the source code:

public class PayloadValidatingInterceptorWithSourceFix extends
		PayloadValidatingInterceptor {

	@Override
	protected Source getValidationRequestSource(WebServiceMessage request) {
		return transformSourceToStreamSourceWithStringReader(request.getPayloadSource());
	}

	@Override
	protected Source getValidationResponseSource(WebServiceMessage response) {
		return transformSourceToStreamSourceWithStringReader(response.getPayloadSource());
	}

	Source transformSourceToStreamSourceWithStringReader(Source notValidatableSource) {
		final Source source;
		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();

			transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
					"yes");
			transformer.setOutputProperty(OutputKeys.INDENT, "no");
			StringWriter writer = new StringWriter();
			transformer.transform(notValidatableSource, new StreamResult(
					writer));

			String transformed = writer.toString();
			StringReader reader = new StringReader(transformed);
			source = new StreamSource(reader);

		} catch (TransformerException transformerException) {
			throw new WebServiceTransformerException(
					"Could not convert the source to a StreamSource with a StringReader",
					transformerException);
		}

		return source;
	}
}

And finally, replace the Spring interceptor bean with this bean:

<bean id="payloadValidatingInterceptorWithSourceFix"
	class="com.redpill.linpro.transport.account.ws.PayloadValidatingInterceptorWithSourceFix">
	<property name="schema"
		value="file:WebContent/WEB-INF/schemas/account-balance-service.xsd" />
	<property name="validateRequest" value="true" />
	<property name="validateResponse" value="true" />
</bean>

When the PayloadValidatingInterceptor work as expected, the interceptor will throw a WebServiceValidationException when you try to send a value that’s not valid according to the XSD schema.

Summary

It’s an elegant framework that’s easy to use.

The validation issues is a minus, but since it’s an open-source project, you always have the chance to replace or extend the classes you want.

The code used in this article was developed in my work time at Redpill Linpro. It can be downloaded here


Redpill Linpro is the leading provider of Professional Open Source services and products in the Nordic region.

Written by Espen

February 28, 2010 at 21:50

Generate JAXB classes from an XSD schema and the schema from an XML sample document

with 10 comments

Introduction

This tutorial demonstrates how to generate an XSD schema from a sample XML document and how to improve the schema before generating JABX classes from it.

The sample document used throughout this tutorial contains an order element as you can see here:

<order>
	<date>2010-01-01</date>
	<id>12345</id>
	<item name="Effective Java" type="BOOK" quantity="1" />
	<item name="Spring in Action" type="BOOK" quantity="1" />
	<item name="Gladiator" type="DVD" quantity="1" />
</order>

The motivation for creating a sample document first, is that it’s more simple and less verbose to write than an XSD schema. You can also generate an XSD schema from JAXB classes, but this tutorial generates JAXB classes from an XSD schema. This is because an XSD schema is language neutral and more feature rich for describing a data contract. For example the ability to add restrictions with regular expressions like this:

<xs:attribute name="number" use="required">
	simpleType>
		<xs:restriction base="xs:string">
			<xs:pattern value="\d{10}" />
		</xs:restriction>
	</xs:simpleType>
</xs:attribute>

Generate XSD schema from XML sample document with Trang

The Trang project lives here: http://code.google.com/p/jing-trang/

You can execute Trang’s Driver class with the XML sample document’s path and the path for the schema that will be generated to generate the schema. Another and probably more common way to generate is with Ant like this target:

<target name="generate-order-schema" description="Generates schema from XML with Trang">
	<mkdir dir="${xsd-folder}" />

	<java jar="${lib-generate-folder}/trang.jar" fork="true">
		<arg value="${xml-example-folder}/order.xml" />
		<arg value="${xsd-folder}/order-generated.xsd" />
	</java>

	<echo message="${xsd-folder}/order-generated.xsd was generated" />
</target>

After you have executed this target, remember to update the folder with the generated file if you’re using Eclipse.

Tweaking the generated XSD schema

I only specified the input file and the path to the generated file when I executed Trang. This results in a very simple XSD without namespace for example. See Trang’s manual for more advanced use. The generated schema looks like this:

<?xml version="1.0" encoding="UTF-8"?>
xmlns:xs="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified">
	<xs:element name="order">
		<xs:complexType>
			<xs:sequence>
				<xs:element ref="date" />
				<xs:element ref="id" />
				<xs:element maxOccurs="unbounded" ref="item" />
			</xs:sequence>
		</xs:complexType>
	</xs:element>
	NMTOKEN" />
	<xs:element name="id" type="xs:integer" />
	<xs:element name="item">
		<xs:complexType>
			<xs:attribute name="name" use="required" />
			<xs:attribute name="quantity" use="required" type="xs:integer" />
			NCName" />
		</xs:complexType>
	</xs:element>
</xs:schema>

The generated schema is an excellent start, but you often want to add namespace, restrictions and often to change the type of some of the fields Trang guessed on. After some manual modifications, my schema looks like this:

<?xml version="1.0" encoding="UTF-8"?>
xmlns:xs="http://www.w3.org/2001/XMLSchema"
	elementFormDefault="qualified" targetNamespace="http://www.redpill-linpro.com/order"
	xmlns:o="http://www.redpill-linpro.com/order">

	<xs:element name="order">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="date" type="xs:date" />
				<xs:element name="id" type="xs:int" />
				<xs:element name="item" maxOccurs="unbounded">
					<xs:complexType>
						<xs:attribute name="name" use="required" type="xs:string" />
						<xs:attribute name="quantity" use="required" type="xs:int" />
						itemType" />
					</xs:complexType>
				</xs:element>
			</xs:sequence>
		</xs:complexType>
	</xs:element>

	<xs:simpleType name="itemType">
		<xs:restriction base="xs:string">
			<xs:enumeration value="BOOK" />
			<xs:enumeration value="DVD" />
			<xs:enumeration value="CD" />
		</xs:restriction>
	</xs:simpleType>
</xs:schema>

Another difference is that it’s only one xs:element in the tweaked version. This is because id or date doesn’t give any value alone, but only together as part of the order element.

A JAXB tips is to create an xs:element with a nested type element inside it. This enables JAXB to add an @XmlRoot annotation above the generated class. This annotation is necessary for JAXB to marshall your objects.

Working with XSD schema inside a good IDE

With an IDE like Eclipse, it’s quite easy to create an XML document from an XSD schema. The XML document below is created with Eclipse. The schema is relative to the document and is highlighted in the XML document. You should consider a URL instead of a relative path for other situations than simple testing.

<?xml version="1.0" encoding="UTF-8"?>
xmlns:o="http://www.redpill-linpro.com/order"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.redpill-linpro.com/order order.xsd ">
	<o:date>2001-01-01</o:date>
	<o:id>0</o:id>
	<o:item name="Effective Java" quantity="3" type="BOOK" />
	<o:item name="Spring in Action" quantity="1" type="BOOK" />
</o:order>

A good IDE that’s aware of the schema will also give you code completion and validation support. This screenshot shows code completion of enumeration values.

Testing the generated JAXB classes

The test below is a simple test that instantiates a JAXB generated order object. Then the object is marshalled to an XML document. The test asserts that the XML document contains some of the data from the order object. After this, it unmarshall the XML document back to an Order object again. Finally, the two order objects are compared against each other.

public class OrderJaxbTest {

	@Test
	public void testOrder() throws Exception {
		Order order = createJaxbOrder();

		String xmlDocument = marshallOrderToXml(order);
		assertTrue(xmlDocument.contains("type=\"CD\" quantity=\"2\" name=\"Effective Java\""));

		Order unmarshalledOrder = unmarshallToOrderFromXml(xmlDocument);
		assertEquals(order.getId(), unmarshalledOrder.getId());
	}

	private Order unmarshallToOrderFromXml(String xmlDocument)
			throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(Order.class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		StringReader reader = new StringReader(xmlDocument);
		Order unmarshalledOrder = (Order) unmarshaller.unmarshal(reader);
		return unmarshalledOrder;
	}

	private Order createJaxbOrder() throws DatatypeConfigurationException {
		Order order = new ObjectFactory().createOrder();
		order.setDate(DatatypeFactory.newInstance()
				.newXMLGregorianCalendar(2010, 5, 5, 12, 0, 0, 0, 1));
		order.setId(1);

		Item item = new Item();
		item.setName("Effective Java");
		item.setQuantity(2);
		item.setType(ItemType.CD);
		order.getItem().add(item);

		return order;
	}

	public String marshallOrderToXml(Order order) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(Order.class);
		Marshaller marshaller =context.createMarshaller();
		StringWriter writer = new StringWriter();
		marshaller.marshal(order, writer);

		return writer.toString();
	}
}

For simplicity, I send an StringWriter to the JAXB marshaller and an StringReader to the JAXB unmarshaller. It’s just to replace the reader and writer if you want to work against the file system.

Summary

To generate a basic version of the schema and then incrementally improve that schema works really well. It exists several frameworks and tools that can generate the schema. I have only evaluated Trang, but it does the job well and it’s an open source project that’s very easy to use. Also, to generate the JAXB classes from an XSD schema ensures that you’re 100% language neutral. JAXB is just one of many frameworks that can parse XML. It’s the Java standard parsing framework and some of its benefits are that it provides type safety and abstracts away the XML parsing behind a Java API.

To generate JAXB classes from an XSD schema is also an excellent start for writing contract-first web services with frameworks like Spring WS and CXF. These frameworks can be configured to use JAXB for the parsing. I will write more about Spring WS in a later article.

The code used in this article was developed in my work time at Redpill Linpro. It can be downloaded from here.


Redpill Linpro is the leading provider of Professional Open Source services and products in the Nordic region.

Written by Espen

February 26, 2010 at 12:36

Posted in Web Services

Tagged with , , ,