Glen Mazza's Weblog

« Streaming Salesforce... | Main | Hosting Spring Boot... »

https://glenmazza.net/blog/date/20180203 Saturday February 03, 2018

Activating XML schema validation for SOAP calls in Apache CXF

Apache CXF allows XML Schema validation of SOAP requests and responses to be activated from either the SOAP client or web service. While validation entails a performance penalty, it also helps validate restrictions not normally enforced by the JAXB databinding framework, such as length restrictions for strings or range restrictions on numeric data.

Activating validation from either the client or the web service side will handle both incoming and outgoing SOAP messages and return Java exceptions to the client should validation errors occur. At least for Tomcat, service-side validation will also result in the exceptions being recorded in the servlet container log.

To show a simple example of schema validation during SOAP calls, let's modify the DoubleIt WSDL to have partially overlapping restrictions on the types of numbers that can be doubled and acceptable ranges of responses that can be returned. This will allow us to see both SOAP request and response validation errors.

Modified DoubleIt's wsdl:types element with size restrictions:

<wsdl:definitions name="DoubleIt"
   ...
   <wsdl:types>
      <xsd:schema targetNamespace="http://www.example.org/schema/DoubleIt">
         <xsd:element name="DoubleIt">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="numberToDouble">
		     <xsd:simpleType>
		       <xsd:restriction base="xsd:int">
		         <xsd:minInclusive value="10"/>
		         <xsd:maxInclusive value="20"/>
		       </xsd:restriction>
   		     </xsd:simpleType>
                  </xsd:element>
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>
         <xsd:element name="DoubleItResponse">
            <xsd:complexType>
               <xsd:sequence>
                  <xsd:element name="doubledNumber">
		     <xsd:simpleType>
		       <xsd:restriction base="xsd:int">
		         <xsd:minInclusive value="24"/>
		         <xsd:maxInclusive value="36"/>
		       </xsd:restriction>
   		     </xsd:simpleType>
                  </xsd:element>
               </xsd:sequence>
            </xsd:complexType>
         </xsd:element>
      </xsd:schema>
   </wsdl:types>
   ...
</wsdl:definitions name="DoubleIt"

To activate Client-side validation: Use the modified WSClient class below. To activate validation, you can either use a programmatic API or add/modify the cxf.xml configuration file in the client's resources folder. Both options are shown below.

package client;

import javax.xml.ws.BindingProvider;
import org.example.contract.doubleit.DoubleItPortType;
import org.example.contract.doubleit.DoubleItService;

public class WSClient {

    public WSClient() {
    }

    public static void main (String[] args) {
        DoubleItService service = new DoubleItService();
        DoubleItPortType port = service.getDoubleItPort();
        System.out.println(doubleItMessage(port, 10));
        System.out.println(doubleItMessage(port, 0));
        System.out.println(doubleItMessage(port, -10));
    }

    public static String doubleItMessage(DoubleItPortType port, int numToDouble) {
        // below line can be used instead of a cxf.xml file
        ((BindingProvider)port).getRequestContext().put("schema-validation-enabled", "true");

        int resp = doubleIt(port, numToDouble);
        return "The number " + numToDouble + " doubled is " + resp;
    }

    public static int doubleIt(DoubleItPortType port, int numToDouble) {
        return port.doubleIt(numToDouble);
    }
}

Second alternative: resources/cxf.xml in DoubleIt client (requires addition of Spring dependency to client's pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jaxws="http://cxf.apache.org/jaxws"
       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.xsd
          http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

   <jaxws:client name="{http://www.example.org/contract/DoubleIt}DoubleItPort"
        createdFromAPI="true">
        <jaxws:properties>
            <entry key="schema-validation-enabled" value="true" />
        </jaxws:properties>
   </jaxws:client>
</beans>
    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
       <version>5.0.3.RELEASE</version>
    </dependency>

After changing the WSDL and client code, be sure to rebuild the project via mvn clean install. A sample truncated CXF validation exception is shown below, CXF's actual command-line output is much more verbose as it automatically outputs stack traces for each exception to the terminal window. For client-side trapping of service-side activated validation exceptions, however, the stack traces will appear only in the service provider's log file.

WARNING: Interceptor for {http://www.example.org/contract/DoubleIt}DoubleItService#{http://www.example.org/contract/DoubleIt}DoubleIt has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: Unmarshalling Error: cvc-minInclusive-valid: Value '20' is not facet-valid with respect to minInclusive '24' for type '#AnonType_doubledNumberDoubleItResponse'. 
	at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:906)
	at org.apache.cxf.jaxb.JAXBEncoderDecoder.unmarshall(JAXBEncoderDecoder.java:712)

Caused by: javax.xml.bind.UnmarshalException
 - with linked exception:
[org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 172; cvc-minInclusive-valid: Value '20' is not facet-valid with respect to minInclusive '24' for type '#AnonType_doubledNumberDoubleItResponse'.]
	at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.handleStreamException(UnmarshallerImpl.java:483)
	at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:417)

To activate Service-side validation: For testing/tutorial purposes, probably best to shut off the client-side validation done above so it will not conflict with or override the validation being activated here. No change to the service bean is needed, just add the schema-validation-enabled property to the cxf-servlet.xml file:

<jaxws:endpoint 
   id="doubleit"
   implementor="service.DoubleItPortTypeImpl"
   wsdlLocation="WEB-INF/wsdl/DoubleIt.wsdl"
   address="/doubleit">
   <jaxws:properties>
       <entry key="schema-validation-enabled" value="true"/>
   </jaxws:properties>
</jaxws:endpoint>

After redeploying the web service provider, running the SOAP client will show both SOAP request and response exceptions logged into the Tomcat logs, as well as the appropriate exceptions returned to the SOAP client (again, even if the client is not explicitly activating schema validation on its side.) I have found the client-side exceptions returned to be somewhat cleaner when the validation is being activated service-side.

Additional Notes

  • To avoid a potential hiccup in validation, make sure you provide the wsdlLocation attribute in the cxf-servlet.xml file, so access to the full schemas are available when doing validation.
  • See this example for using a JAX-WS ValidationEventHandler to customize the information returned by validation exceptions.
  • As suggested here, CXF's JMX integration can be used to estimate the performance hit that validation entails.
  • Handling validation within the JAXB-generated Java classes using Java Bean Validation is another option.
  • For standalone JAXB (i.e., outside of JAX-WS) marshalling/unmarshalling validation, see the JAXB tutorial as well as this StackOverflow example.

Comments

Post a Comment: