Abstract Schema Types
Overview
When dealing with abstract schema types, you may encounter issues with Axis. The issue is not so much that it "can't deal with abstract classes" as it is that you cannot actually create an instance document from an element which is abstract, or its type is abstract, as defined in the schema specification
.
Instance documents using abstract elements or elements with abstract types must specify an xsi:type of a concrete type. Axis should honor the xsi:type information when deserializing and create an instance of a concrete type.
This all works fine in Axis on the client side. However, the service-side has problems handling these types; let's first walk through how this works, and then discuss the service-side issue and how to fix it.
Below is an example service created to illustrate this, you can also download the Introduce service
and try it yourself.
Here is a modification to the service
to illustrate the fixes necessary to also send abstract types in requests.
Example Schema with Abstract Types
Extracted from the schema specification
:
<schema xmlns="http:
targetNamespace="http:
xmlns:target="http:>
<complexType name="Vehicle" abstract="true"/>
<complexType name="Car">
<complexContent>
<extension base="target:Vehicle"/>
</complexContent>
</complexType>
<complexType name="Plane">
<complexContent>
<extension base="target:Vehicle"/>
</complexContent>
</complexType>
<element name="transport" type="target:Vehicle"/>
</schema>
Example Service Method Returning an Abstract Type
public com.example.cars.Vehicle getVehicle(java.lang.String string) throws RemoteException {
if (string.equals(Constants.CAR)) {
return new Car();
} else if (string.equals(Constants.PLANE)) {
return new Plane();
} else {
throw new RemoteException("Unsupported transport type!");
}
}
Example Client Invocation
Car car = (Car) client.getVehicle(Constants.CAR);
System.out.println("Got car:" + car);
Plane plane = (Plane) client.getVehicle(Constants.PLANE);
System.out.println("Got plane:" + plane);
Example Client Invocation Output
Running the Grid Service Client
Got car:com.example.cars.Car@1
Got plane:com.example.cars.Plane@1
SOAP Messages
Notice in both responses, the element is "transport" and the xsi:type specifies the concrete instance.
Captured SOAP Response for client.getVehicle(Constants.CAR)
<soapenv:Body>
<GetVehicleResponse xmlns="http:>
<ns1:transport xsi:type="ns1:Car" xmlns:ns1="http:/>
</GetVehicleResponse>
</soapenv:Body>
Captured SOAP Response for client.getVehicle(Constants.PLANE)
<soapenv:Body>
<GetVehicleResponse xmlns="http:>
<ns1:transport xsi:type="ns1:Plane" xmlns:ns1="http:/>
</GetVehicleResponse>
</soapenv:Body>
Problems on the Service-side
The client is fully able to send and receive these abstract types. However, the service does not understand how to deserialize these abstract types be default. Here is an email thread on the Globus mailing list
that discusses this problem. If you add a method to the service which sends an abstract type in the request, the service will raise an error like:
Sending plane
AxisFault
faultCode: {http:faultSubcode:
faultString: org.xml.sax.SAXException: Unable to create JavaBean of type com.example.cars.Vehicle. Missing default constructor? Error was: java.lang.InstantiationException.
faultActor:
faultNode:
faultDetail:
{http:at org.apache.axis.encoding.ser.BeanDeserializer.startElement(BeanDeserializer.java:124)
at org.apache.axis.encoding.DeserializationContext.startElement(DeserializationContext.java:1048)
at org.apache.axis.message.SAX2EventRecorder.replay(SAX2EventRecorder.java:165)
at org.apache.axis.message.MessageElement.publishToHandler(MessageElement.java:1140)
at org.apache.axis.message.RPCElement.deserialize(RPCElement.java:238)
at org.apache.axis.message.RPCElement.getParams(RPCElement.java:386)
at org.apache.axis.providers.java.RPCProvider.processMessage(RPCProvider.java:148)
at org.apache.axis.providers.java.JavaProvider.invoke(JavaProvider.java:319)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
at org.apache.axis.handlers.soap.SOAPService.invoke(SOAPService.java:450)
at org.apache.axis.server.AxisServer.invoke(AxisServer.java:285)
at org.globus.wsrf.container.ServiceThread.doPost(ServiceThread.java:676)
at org.globus.wsrf.container.ServiceThread.process(ServiceThread.java:397)
at org.globus.wsrf.container.ServiceThread.run(ServiceThread.java:302)
{http:
org.xml.sax.SAXException: Unable to create JavaBean of type com.example.cars.Vehicle. Missing default constructor? Error was: java.lang.InstantiationException.
at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:221)
at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:128)
at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087)
at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source)
at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)
at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
at javax.xml.parsers.SAXParser.parse(SAXParser.java:375)
at org.apache.axis.encoding.DeserializationContext.parse(DeserializationContext.java:227)
at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:645)
at org.apache.axis.Message.getSOAPEnvelope(Message.java:424)
at org.apache.axis.message.addressing.handler.AddressingHandler.processClientResponse(AddressingHandler.java:305)
at org.apache.axis.message.addressing.handler.AddressingHandler.invoke(AddressingHandler.java:110)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
at org.apache.axis.client.AxisClient.invoke(AxisClient.java:190)
at org.apache.axis.client.Call.invokeEngine(Call.java:2727)
at org.apache.axis.client.Call.invoke(Call.java:2710)
at org.apache.axis.client.Call.invoke(Call.java:2386)
at org.apache.axis.client.Call.invoke(Call.java:2309)
at org.apache.axis.client.Call.invoke(Call.java:1766)
at gov.nih.nci.cagrid.abstracttest.stubs.bindings.AbstractTestPortTypeSOAPBindingStub.sendTransport(AbstractTestPortTypeSOAPBindingStub.java:884)
at gov.nih.nci.cagrid.abstracttest.client.AbstractTestClient.sendTransport(AbstractTestClient.java:145)
at gov.nih.nci.cagrid.abstracttest.client.AbstractTestClient.main(AbstractTestClient.java:118)
You basically need to edit the service's server-config.wsdd and add type mappings for each of the "concrete types". See the information below on the specifics. Here is the complete working example service
.
Example Method Sending an Abstract Type
public void sendTransport(com.example.cars.Vehicle transport) throws RemoteException {
System.out.println("Got the transport:" + transport);
}
Example Client Invocation
System.out.println("Sending plane");
client.sendTransport(plane);
System.out.println("Sending car");
client.sendTransport(car);
Example Client Invocation Output
Running the Grid Service Client
Got car:com.example.cars.Car@1
Got plane:com.example.cars.Plane@1
Sending plane
Sending car
Output on the Service
Got the transport:com.example.cars.Plane@1
Got the transport:com.example.cars.Car@1
Captured SOAP Request for sendTransport(plane)
<soapenv:Body>
<SendTransportRequest xmlns="http:>
<transport>
<ns1:transport xsi:type="ns1:Plane" xmlns:ns1="http:/>
</transport>
</SendTransportRequest>
</soapenv:Body>
Additions to server-config.wsdd Necessary to Get it to Work
Each concrete type needs an entry in the server-config.wsdd, specifying the serializers and deserializers which should be used.
<typeMapping xmlns:ns="http:
qname="ns:Car"
type="java:com.example.cars.Car"
serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
encodingStyle=""/>
<typeMapping xmlns:ns="http:
qname="ns:Plane"
type="java:com.example.cars.Plane"
serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
encodingStyle=""/>