Skip to main content

How to deal with derived classes in a JAX-WS call

1 reply [Last post]
gkacy
Offline
Joined: 2005-12-14
Points: 0

Does anybody know the answer to the problem below. Don't get discouraged by size of this posting. It's all very simple. In the nutshell it boils to a question whether there is a way to interact/affect the marshaller or context of the default marshaller used by JAX-WS.

Suppose we have two classes A and B. B derives from A

---------------------

<br />
package com.greg.test;</p>
<p>import javax.xml.bind.annotation.XmlRootElement;</p>
<p>@XmlRootElement<br />
public class ClassA {</p>
<p>  @XmlElement<br />
  public String strA;<br />
}</p>
<p>------------------</p>
<p>package com.greg.test;</p>
<p>import javax.xml.bind.annotation.XmlRootElement;<br />
import javax.xml.bind.annotation.XmlElement;</p>
<p>@XmlRootElement<br />
public class ClassB extends ClassA {</p>
<p>  @XmlElement<br />
  public String strB;<br />
}</p>
<p>

-------------------

Now we have a simple JAX-WS web service that returns ClassA

<br />
Stateless<br />
@WebService(name = "SampleClassTesterIfc",<br />
    targetNamespace = Constants.TARGET_NAMESPACE,<br />
    serviceName = "SampleClassTester")<br />
@SOAPBinding(style = SOAPBinding.Style.RPC)<br />
public class SampleClassTester {</p>
<p>  @WebMethod(operationName = "testCall",<br />
      action = "testCall")<br />
  @WebResult(name = "testCall",<br />
      targetNamespace = Constants.TARGET_NAMESPACE,<br />
      partName = "testCall")</p>
<p>  public ClassA testCall() {<br />
    ClassB b = new ClassB();<br />
    b.strA = "string A";<br />
    b.strB = "string B";</p>
<p>    System.out.println(toXML(b, ClassB.class));</p>
<p>    return b;<br />
  }<br />

------------------

<br />
@WebService(name = "SampleClassTesterIfc",<br />
            targetNamespace = Constants.TARGET_NAMESPACE)<br />
@SOAPBinding(style = SOAPBinding.Style.RPC)<br />
public interface SampleClassTesterIfc {</p>
<p>  @WebMethod(operationName = "testCall",<br />
      action = "testCall")<br />
  @WebResult(name = "testCall",<br />
      targetNamespace = Constants.TARGET_NAMESPACE,<br />
      partName = "testCall")<br />
  public ClassA testCall();</p>
<p>}<br />

--------------------

A simple WS client makes a call:

<br />
SampleClassTester client;<br />
String userRecordEndpoint = "http://10.18.28.85:9090/zsnosi/beta/SampleClassTester?WSDL";</p>
<p>try {<br />
  client = new SampleClassTester(new URL(userRecordEndpoint),<br />
          new QName(Constants.TARGET_NAMESPACE, "SampleClassTester"));<br />
  SampleClassTesterIfc ifc = (SampleClassTesterIfc) client.getSampleClassTesterIfcPort();<br />
  ClassA a = ifc.testCall();<br />
  System.out.println(toXML(a, ClassB.class));<br />
} catch (MalformedURLException e) {<br />
  e.printStackTrace();<br />
}<br />
System.out.println("DONE");<br />

---
Debug Output to xml

<br />
// output utility<br />
  public static String toXML(Object obj, Class ... clazzez) {<br />
    try {<br />
      JAXBContext jc = createContext(obj, clazzez);<br />
      Marshaller marshaller = jc.createMarshaller();<br />
      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);</p>
<p>      StringWriter writer = new StringWriter();<br />
      marshaller.marshal(obj, writer);<br />
      return writer.toString();<br />
    } catch (JAXBException exc) {<br />
      AppLogger.severe("JAXBXMLSerializer", "toXML", "Exception", exc);<br />
      return "ERROR";<br />
    }<br />
  }</p>
<p>  public static JAXBContext createContext(Object obj, Class ... clazzez) throws JAXBException {<br />
    Class[] classes = new Class[clazzez.length + 1];</p>
<p>    classes[0] = obj.getClass();<br />
    for (int i = 0; i < clazzez.length; i++) {<br />
      classes[1 + i] = clazzez[i];<br />
    }<br />
    return JAXBContext.newInstance(classes);<br />
  }<br />
}</p>
<p>

-------

The output on the server side looks like this (I have a trace handler installed to print in/out SOAP):

</p>
<p><?xml version="1.0" encoding="UTF-8" standalone="yes"?></p>
<p>    string A<br />
    string B</p>
<p>(Outbound SOAP Message)<br />
<?xml version="1.0" encoding="UTF-8"?></p>
<p>        string A</p>
<p>

The output on the client is :

<br />
(Inbound SOAP Message)<br />
<?xml version="1.0" encoding="UTF-8"?></p>
<p>        string A</p>
<p><?xml version="1.0" encoding="UTF-8" standalone="yes"?></p>
<p>    string A</p>
<p>DONE</p>
<p>

On the server side I managed to marshal to XML (as expected) a full ClassB object

<br />
<?xml version="1.0" encoding="UTF-8" standalone="yes"?></p>
<p>    string A<br />
    string B</p>
<p>

however when this object was "sent" over the wire in a WS call we have lost the derived class data

<br />
<?xml version="1.0" encoding="UTF-8" standalone="yes"?></p>
<p>    string A</p>
<p>

Obviously in the first case the object was marshaled because I have provided the ClassB.class for the JAXBContext.

Would there be a way to provide the same information for the default JAXBContext that is used in JAX-WS call?

Or anybody has other ideas how this "derived class" web service problem can be solved ?

Thanks

Greg

I am aware that I could wrap my class and use @XmlElements
annotation. This would solve the problem. The issue is that I dont know the derived classes at compile time and using XmlElements will not cut it.

Message was edited by: gkacy

Message was edited by: gkacy

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
gkacy
Offline
Joined: 2005-12-14
Points: 0

I think I have found an answer:

Seems like there is a request for a feature related to my problem ....

https://jax-ws.dev.java.net/issues/show_bug.cgi?id=282