Skip to main content

XSLT Transformations with Apache CXF

This blog post might be outdated!
This blog post was published more than one year ago and might be outdated!
· 2 min read

When providing and consuming webservices both sides do their best to fulfill the WSDL contract. But what if one side can not fulfill the contract? To be concrete, the client sends us SOAP requests but omits the application's namespace. So instead of:

[
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:webservice.application.com:types">
<soapenv:Header/>
<soapenv:Body>
<urn:getUser>
<urn:accountId>abcde</urn:accountId>
</urn:getUser>
</soapenv:Body>
</soapenv:Envelope>
]

they send:

[
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<getUser>
<accountId>abcde</accountId>
</getUser>
</soapenv:Body>
</soapenv:Envelope>
]

Our server application using Spring and Apache CXF answers correctly:

[
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<soap:Fault>
<faultcode>soap:Client</faultcode>
<faultstring>Unexpected wrapper element getUser found.
Expected {urn:webservice.application.com:types}getUser.
</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>
]

So what now? Disabling schema validation does not get us far. The requests would be processed but all arguments are null. That's where Apache CXF's XSLT feature comes into play.

The XSLT feature gives us the possibility to manipulate incoming requests and outgoing responses by using XSLT. What do you have to do to get the requests processed correctly? First, we add an XSLT feature for incoming requests to the existing endpoint in cxf-servlet.xml:

[
<jaxws:endpoint xmlns:tns="urn:webservice.application.com:wsdl"
id="webserviceImpl" address="/Webservice" serviceName="tns:webservice"
endpointName="tns:WebserviceSoapPort"
implementor="de.bitexpert.application.WebserviceImpl">
<!-- added -->
<jaxws:features>
<ref bean="normalizeRequests"/>
</jaxws:features>
<!-- /added -->
</jaxws:endpoint>

<!-- added -->
<bean id="normalizeRequests"
class="org.apache.cxf.feature.transform.XSLTFeature">
<property name="inXSLTPath" value="normalizeRequest.xsl" />
</bean>
<!-- /added -->
]

This is our XSL transformation in normalizeRequest.xsl:

[
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="https://www.w3.org/1999/XSL/Transform"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:urn="urn:webservice.idm.customer.com:types">
<xsl:output
method="xml"
omit-xml-declaration="yes"
indent="yes"
standalone="omit"/>
<!-- copy root element as it is -->
<xsl:template match="/soapenv:Envelope" priority="2">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- copy soapenv prefixed elements as they are -->
<xsl:template match="soapenv:*" priority="1">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- copy urn prefixed elements as they are -->
<xsl:template match="urn:*" priority="1">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<!-- create other elements with urn namespace prefix -->
<xsl:template match="*">
<xsl:element name="urn:{name()}"
namespace="urn:webservice.application.com:types">
<xsl:apply-templates></xsl:apply-templates>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
]

TLDR; this XSLT copies all nodes as they are, except those whose namespace is the default namespace (=not set). Those nodes will be transformed to our application's namespace. That's all. After deploying our application and sending:

[
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<getUser>
<accountId>abcde</accountId>
</getUser>
</soapenv:Body>
</soapenv:Envelope>
]

our application responds with:

[
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUserResponse xmlns="urn:webservice.application.com:types">
<userData>
<accountId>abcde</accountId>
<email>John.Doe@acme.com</email>
<firstname>John</firstname>
<lastname>Doe</lastname>
<roles>
<role>A</role>
<role>B</role>
<role>C</role>
</roles>
</userData>
</getUserResponse>
</soap:Body>
</soap:Envelope>
]

Mission accomplished!