Thursday, June 03, 2010

Stop AXIS output of anonymous complex types in xsi:type

Keywords:
apache axis xsi:type anonymous complex type .NET "No type definition found for the type referenced by the attribute 'xsi:type'"

Problem:
The schema definition for a response element may define the complex type "inline" rather than by reference. This is known as an "anonymous type". Eg:
<xs:element name="MyResponse">
    <xs:annotation>
        <xs:documentation>This is the root element of the Response.</xs:documentation>
    </xs:annotation>
    <xs:complexType>
        <xs:sequence>
            <xs:element name="PropertyOne" type="xs:string" minOccurs="0"/>
            <xs:element name="PropertyTwo" type="xs:string" minOccurs="0"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

When axis generates the SOAP response (with the default settings) this includes xsi:type attributes. Eg:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xsi:type="ns1:MyResponse" xmlns="http://example.com/service" xmlns:ns1="http://example.com/service">
        ...

This is accepted by AXIS-generated client code but in .NET (and other schema-validating tools such as XMLSpy) you'll get an error along the lines of
No type definition found for the type referenced by the attribute 'xsi:type'='ns1:MyResponse' of element <MyResponse>

The error inidicates validation is probably doing the right thing with the xsi:type attribute and checking it - there is actually no type with this name, it's anonymous. Can the behavour in AXIS be changed?

Solution:
Thankfully there is a configuration setting in AXIS to stop this output - sendXsiTypes, which is true by default:
<parameter name="sendXsiTypes" value="false"/>

There are a number of ways to get this setting in there. I'd recommend creating a wsdd deploy file for just the globalConfiguration and deploy this to the AXIS AdminServlet as you would do with the deploy.wsdd definitions for the other service(s).
So an example globalConfig-deploy.wsdd:
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
    <globalConfiguration>
        ...
        <parameter name="sendXsiTypes" value="false" />
        ...
    </globalConfiguration>
</deployment>

You'll find the MyApp/WEB-INF/server-config.wsdd will then be updated and the SOAP excludes this attribute as advertised:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xmlns="http://example.com/service">
        ...


The alternative to this (if you do want the xsi:type attributes) is to define explicit types for your root response elements. Eg:
<xs:element name="MyResponse" type="MyResponseType">
    <xs:annotation>
        <xs:documentation>This is the root element of the Response.</xs:documentation>
    </xs:annotation>
</xs:element>
<xs:complexType name="MyResponseType">
    <xs:annotation>
        <xs:documentation>This is the type for the root element of the Response.</xs:documentation>
    </xs:annotation>
    <xs:sequence>
        <xs:element name="PropertyOne" type="xs:string" minOccurs="0"/>
        <xs:element name="PropertyTwo" type="xs:string" minOccurs="0"/>
    </xs:sequence>
</xs:complexType>

The XML from AXIS will then include xsi:type but the value will be correct - referencing a type that does exist. Eg:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <soapenv:Body>
     <MyResponse xsi:type="ns1:MyResponseType" xmlns="http://example.com/service" xmlns:ns1="http://example.com/service">
        ...