If you’re exposing existing Java code containing JAXB generated value objects as a Java First JAX-WS web-service with Apache CXF you might run into an unexpected problem.
If you need to expose a new or an existing application as a web-service in a reasonably controlled environment where you can safely manage the knock on effects of your WSDL changing, Java first JAX-WS web-services are about as easy as it gets. Apache CXF provides a robust and highly configurable way to go about it.
You might encounter an unexpected hump in that smooth road if your existing service already contains some JAXB generated classes though. You might see errors like the following when your web application starts up:-
SEVERE: Schema element {https://www.devsumo.com/exchangerateservice}currencyCodes references undefined type currencyCodesType for service {https://www.devsumo.com/exchangerateservice}ExchangeRateServiceImplService.
Or errors like the following when importing your freshly minted WSDL into a tool like SoapUI:-
Source: https://www.devsumo.com/exchangerateservice?wsdl Error: Could not find type 'currencyCodesType'. Do you mean to refer to the type named currencyCodesType@https://www.devsumo.com/exchangerateservice (in exchangerateservice_3Fwsdl)?
You might have thought that JAXB generated classes would be the last ones you’d encounter problems with when trying to generate a JAXWS web service? Think again!
A simple example
Let’s say we’re creating a new web-service to leverage some existing code for obtaining currency exchange rates. We put together the following Java interface for our service:-
import javax.jws.WebParam; import javax.jws.WebService; @WebService(targetNamespace = "https://www.devsumo.com/exchangerateservice") public interface ExchangeRateService { public ExchangeRate getExchangeRate( @WebParam(name="homeCurrencyCode") String homeCurrencyCode, @WebParam(name="alienCurrencyCode") String alienCurrencyCode); }
Our operation accepts two currency code names, e.g. USD or GBP and returns an ExchangeRate value object from our existing code-base. It looks like this:-
public class ExchangeRate { private CurrencyCodeType homeCurrency = null; private CurrencyCodeType alienCurrency = null; private Double rate = null; private Date lastUpdated = null; public ExchangeRate() {) // Getters and Setters }
Along with the rate and effectivity date our back-end code returns a CurrencyCodeType object for each currency, containing the country, full currency name and ISO4217 currency number. It looks up these values from an XML file contained in the application:-
<currencyCodes> <currencyCode> <countryName>UNITED KINGDOM</countryName> <currencyName>Pound Sterling</currencyName> <currencyCode>GBP</currencyCode> <currencyNumber>826</currencyNumber> </currencyCode> <currencyCode> <countryName>UNITED STATES</countryName> <currencyName>US Dollar</currencyName> <currencyCode>USD</currencyCode> <currencyNumber>840</currencyNumber> </currencyCode> … </currencyCodes>
Data like this, slow-moving static data readily available in XML format, is an ideal use-case for JAXB. All we need is a simple schema definition:-
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="currencyCodes" type="currencyCodesType"/> <xsd:complexType name="currencyCodesType"> <xsd:sequence> <xsd:element name="currencyCode" type="currencyCodeType" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="currencyCodeType"> <xsd:sequence> <xsd:element name="countryName" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyName" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyCode" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyNumber" type="xsd:string" minOccurs="0" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:schema>
Run this through the JAXB tool-chain and presto, we have our value objects, and a few lines of Java will load the file into our application:-
JAXBContext jaxbContext = JAXBContext.newInstance( "com.devsumo.exchangerateservice.currencies"); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); JAXBElement<CurrencyCodesType> currencyList = (JAXBElement<CurrencyCodesType>)unmarshaller.unmarshal( CurrencyCodes.class.getResourceAsStream("/currencyCodes.xml"));
The problem
What happens when you try to expose these classes in a web-service using CXF is that it gets its namespaces in a bit of a twist:-
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.devsumo.com/exchangerateservice" elementFormDefault="unqualified" targetNamespace="https://www.devsumo.com/exchangerateservice" version="1.0"> <xs:import namespace="https://www.devsumo.com/exchangerateservice"/> <xs:element name="getExchangeRate" type="tns:getExchangeRate"/> <xs:element name="getExchangeRateResponse" type="tns:getExchangeRateResponse"/> <xs:complexType name="getExchangeRate"> … </xs:complexType> <xs:complexType name="getExchangeRateResponse"> … </xs:complexType> </xs:schema> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="https://www.devsumo.com/exchangerateservice" version="1.0"> <xs:element name="currencyCodes" type="currencyCodesType"/> <xs:complexType name="exchangeRate"> … </xs:complexType> <xs:complexType name="currencyCodeType"> … </xs:complexType> <xs:complexType name="currencyCodesType"> … </xs:complexType> </xs:schema>
Yes, all the required objects are present, but our single namespace has got split into two schemas having the same namespace, with the break starting at our first JAXB generated class.
The solution
You might have thought putting namespaces in the schema for your simple JAXB classes would be overkill, but unfortunately CXF isn’t just taking the value-objects and rolling them into the newly generated schema – it wants to reference the schema that originally generated them, and if there’s no namespace information it kinda falls apart.
Let’s add the minimum namespace support to our JAXB schema:-
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="https://www.devsumo.com/currencyCodes" xmlns:tns="https://www.devsumo.com/currencyCodes" elementFormDefault="unqualified"> <xsd:element name="currencyCodes" type="tns:currencyCodesType"/> <xsd:complexType name="currencyCodesType"> <xsd:sequence> <xsd:element name="currencyCode" type="tns:currencyCodeType" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="currencyCodeType"> <xsd:sequence> <xsd:element name="countryName" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyName" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyCode" type="xsd:string" minOccurs="0" maxOccurs="1"/> <xsd:element name="currencyNumber" type="xsd:string" minOccurs="0" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:schema>
And add the namespace to our XML document – because we’ve gone with elementFormDefault=”unqualified” we only need to tag the container element with the namespace so the file changes are minimised:-
<ns1:currencyCodes xmlns:ns1="https://www.devsumo.com/currencyCodes"> <currencyCode> <countryName>UNITED KINGDOM</countryName> <currencyName>Pound Sterling</currencyName> <currencyCode>GBP</currencyCode> <currencyNumber>826</currencyNumber> </currencyCode> <currencyCode> <countryName>UNITED STATES</countryName> <currencyName>US Dollar</currencyName> <currencyCode>USD</currencyCode> <currencyNumber>840</currencyNumber> </currencyCode> </ns1:currencyCodes>
Now, if we rebuild and redeploy our application, we’re all good:-
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.devsumo.com/exchangerateservice" xmlns:ns1="https://www.devsumo.com/currencyCodes" elementFormDefault="unqualified" targetNamespace="https://www.devsumo.com/exchangerateservice" version="1.0"> <xs:import namespace="https://www.devsumo.com/currencyCodes"/> <xs:element name="getExchangeRate" type="tns:getExchangeRate"/> <xs:element name="getExchangeRateResponse" type="tns:getExchangeRateResponse"/> <xs:complexType name="getExchangeRate"> … </xs:complexType> <xs:complexType name="getExchangeRateResponse"> … </xs:complexType> <xs:complexType name="exchangeRate"> … </xs:complexType> </xs:schema> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="https://www.devsumo.com/currencyCodes" targetNamespace="https://www.devsumo.com/currencyCodes" version="1.0"> <xs:element name="currencyCodes" type="tns:currencyCodesType"/> <xs:complexType name="currencyCodeType"> … </xs:complexType> <xs:complexType name="currencyCodesType"> … </xs:complexType> </xs:schema>
Now we’ve got two schemas but this time all our freshly minted service objects are in the same schema and the original schema definition for our JAXB generated currency code objects are in their own, separate schema.