Serializing an Axis JavaBean object to XML

Several weeks ago I needed to implement an Axis 1.1 web service server. Axis is a really nice package for abstracting the infinitely complex specifications surrounding current web services. Why is it that once a technology gets standardized it grows in complexity by an order of magnitude? Ah well, that’s a rant for another time. Anyway, as I was saying, Axis does a really nice job of abstracting all this complexity.

I had XML schema’s for the messages that were to be passed and returned by the web service and I created a WSDL describing the operations I would make available for these messages. I did this by importing these schemas into a Schema project in BEA WebLogic Workshop. I then used Workshop to define the methods I wanted to export and asked Workshop to create a WSDL describing these operations. This was quick and simple. Kudos BEA.

Next I ran the generated WSDL through the Axis tool WSDL2Java to generate classes used to interface with Axis and the SOAP messages received. Axis complained about the WSDL generated by BEA Workshop. The error messages generated were not particularly useful to me but by trial and error I determined that Axis was not happy with some of the liberties the BEA tool was taking with respect to namespaces. I manually corrected the WSDL, generated the Axis classes, and was on my way.

Shout out to BEA: The WSDL generated by your tools is not very useful if it is not portable to other Web Service tools. I was using the 8.1 version of Workshop so maybe this is fixed in the new 9.x Eclipse based versions.

OK, now I’m cooking. I quickly add my server specific code and I’m testing the service in short order. Excluding the time I spent dinking around with the WSDL file this whole process took about an hour. Pretty impressive.

Finally, I get a requirement to log the incoming XML data message. Not the whole SOAP message but the payload represented by the Axis generated classes passed to my server object. No problem right? I’ll just ask Axis to serialize the JavaBean class to XML and I’m done. After spending an hour or so looking I just don’t see any way to do this and it isn’t even mentioned in the Axis documentation. Time to Google the Internet for help.

I find a lot of other people asking this same question but no one seems to be answering them. I do find a blog on IBM’s site addressing this subject but it basically just states that serializing an Axis object to XML is non-intutive without offering any insight about how to accomplish it. Great!

So I delve into the Axis source (thank God for open source) and after a couple of hours I’ve cobbled together a generic utility routine to handle this which I’ll share here. I’m not claiming that this is the best way to accomplish this but it seems to work pretty well.

Shout out to Axis Developers: Guys you’ve got to make this easier. Take a look at how simple this is in Apache XMLBeans.

First I created an Exception class to be thrown if an error is encountered.

public class AxisObjectException extends Exception
{
    public AxisObjectException()
    {
        super();
    }

public AxisObjectException(final String message, final Throwable cause)
    {
        super(message, cause);
    }

public AxisObjectException(final String message)
    {
        super(message);
    }

public AxisObjectException(final Throwable cause)
    {
        super(cause);
    }
}

Finally, here is the utility code. If there is a better way of doing this please let me know.

/**
 * This is a utility class for working with Axis generated objects.
 *
 * @author Bob Withers  7/6/06
 */

import java.io.StringWriter;
import java.lang.reflect.Method;

import javax.xml.namespace.QName;

import org.apache.axis.MessageContext;
import org.apache.axis.description.TypeDesc;
import org.apache.axis.encoding.SerializationContextImpl;
import org.apache.axis.encoding.ser.BeanSerializer;
import org.apache.axis.server.AxisServer;
import org.xml.sax.helpers.AttributesImpl;

public class AxisObjectUtil
{
    /**
     * Convert an Axis data object (as generated by WSDL2Java) to an XML string.
     *
     * @param obj The xis JavaBean object.
     * @param removeNamespaces If true all namespace attributes will be removed from
     *                  the returned XML string.
     *
     * @return A string containing the XML of the seriaslized Axis JavaBean object.
     * @throws AxisObjectException If an error is encountered while serializing
     *                  the Axis JavaBean.
     */
    public static String serializeAxisObject(final Object obj, final boolean removeNamespaces,
                                            final boolean prettyPrint) throws AxisObjectException
    {
        final StringWriter outStr = new StringWriter();
        final TypeDesc typeDesc = getAxisTypeDesc(obj);
        QName qname = typeDesc.getXmlType();
        String lname = qname.getLocalPart();
        if (lname.startsWith(">") && lname.length() > 1)
            lname = lname.substring(1);

qname = removeNamespaces ? new QName(lname)
			: new QName(qname.getNamespaceURI(), lname);
        final AxisServer server = new AxisServer();
        final BeanSerializer ser = new BeanSerializer(obj.getClass(), qname, typeDesc);
        final SerializationContextImpl ctx = new SerializationContextImpl(outStr, new MessageContext(server));
        ctx.setSendDecl(false);
        ctx.setDoMultiRefs(false);
        ctx.setPretty(prettyPrint);
        try
        {
            ser.serialize(qname, new AttributesImpl(), obj, ctx);
        }
        catch (final Exception e)
        {
            throw new AxisObjectException("Unable to serialize object "
                                           + obj.getClass().getName(), e);
        }

String xml = outStr.toString();

if (removeNamespaces)
        {
            // remove any namespace attributes
            xml = xml.replaceAll(" xmlns[:=].*?\".*?\"", "")
			.replaceAll(" xsi:type=\".*?\"", "");
        }

return(xml);
    }

/**
     * Return the Axis TypeDesc object for the passed Axis JavaBean.
     *
     * @param obj The Axis JavaBean object.
     *
     * @return The Axis TypeDesc for the JavaBean.
     * @throws AxisObjectException If the passed object is not an Axis JavaBean.
     */
    public static TypeDesc getAxisTypeDesc(final Object obj) throws AxisObjectException
    {
        final Class objClass = obj.getClass();
        try
        {
            final Method methodGetTypeDesc = objClass.getMethod("getTypeDesc", new Class[] {});
            final TypeDesc typeDesc = (TypeDesc) methodGetTypeDesc.invoke(obj, new Object[] {});
            return(typeDesc);
        }
        catch (final Exception e)
        {
            throw new AxisObjectException("Unable to get Axis TypeDesc for " 
		+ objClass.getName(), e);
        }
    }
}
Advertisements

38 Responses to Serializing an Axis JavaBean object to XML

  1. Already Done. Try Axis2’s Data Binding. It’s as easy as XMLBeans data binding.

  2. bwithers says:

    Thanks Davanum. Good news. Unfortunately, for the time being, I’m required to use Axis 1.1 but I look forward to the day we can upgrade the a more recent version.

  3. Tom Turner says:

    Bob, thank you, I’ve been searching for hours for an answer to this. Haven’t seen much. Time permitting, I will try out your solution to see how the XML comes out. I’m most interested in how the xml comes out related to the XSD I’ve been given.

    I first tried, XStream but that doesn’t support XML Schema.
    XStream xstream = new XStream();
    String loanXML = xstream.toXML(loan);
    log.debug(“xstream=” + loanXML);

    and then default Castor.
    StringWriter out = new StringWriter();
    Marshaller.marshal(loan, out);
    log.debug(out.toString());

    Which work great and for you is perfect, since you are just logging. But my XML Attributes came out as Elements so that won’t satisfy the spec. Also, my XSD has the 1st character of the attribute/element names as UpperCase, which doesn’t meet the naming convention of java fields, which start with a lowerCase.

    So, I settled with auto-generating a Castor Mapping.xml file. And then tweaking the mapping to handle my upper case needs (plus some other naming convention changes too).
    StringWriter myWriter = new StringWriter();
    Marshaller m1 = new Marshaller( myWriter );
    Mapping mapping = new Mapping();
    mapping.loadMapping(“FSICLU/wsdl/castor_mapping.xml”);
    m1.setMapping( mapping );
    m1.marshal( loan );
    log.debug(myWriter.toString());

    I thought about XMLBeans, but that doesn’t help me because Axis 1.3 has already unmarshaled the object into a POJO and not into the compiled XSD java files created by scomp.

    I saw the article on integrating Castor with Axis and I might pursue that. This approach auto-generates java POJO’s based on the XSD. Just like xmlbeans scomp (I think). But then tells Axis to deserialize right into the Castor object. At that point, I think you can do a toString() or toXML() or something to get the XML. It’s a lot of work to go through and maybe I won’t get the exact behaviour I want.

    Odd that I can’t find anyone that can simply do
    StringWriter out = new StringWriter();
    Marshaller.marshal(loan, out, “path/nameofSchema.xsd”);
    log.debug(out.toString());

    Castor seems close but it’s using that Mapping.xml instead of the real XSD.

    Let me know if you have any ideas. And thanks again for the code.

  4. Tom Turner says:

    Well, you have totally made my day (evening really). The xml string is spot on to the WSDL/XSD. Now I can reliably put that on the queue.

    I guess it makes sense, the data seems to come from that static code related to the TypeDesc. Your method, getAxisTypeDesc(), is getting that TypeDesc info.

    Thanks again.

  5. bwithers says:

    Well Tom you’re welcome and thank you. It makes my day that you’ve found the code to be useful.

  6. James McGill says:

    This was helpful, but I am now trying to figure out how to unmarshal the resulting string and get teh Axis object back. Any hints? I’m about to give up and just map yet another object using XStream or Castor.

  7. Nuran Jetha says:

    I painfully managed to figure this out myself, but now I’m looking for an easy way to replace the XSI types. It looks like you are just replacing all of them with empty string, is there another way to do it that you know of, maybe something internal within the SerializationContext class?

  8. bwithers says:

    Well it’s been a long time since I’ve looked at this code. We ended up not using Axis so I really have kept up or maintained this routine. I guess it is not clear to me what you’d like to replace XSI types with. The serialized XML string will contain the appropriate XSI strings. In my case I wanted the option to have them removed so I added a boolean parameter that would do that.

  9. Nuran Jetha says:

    Sorry, what I meant to say was I wanted them removed. It looks like you are doing it with a regular expression. This works fine, but just wondering if there is a way to do it without a regular expression swap.

  10. bwithers says:

    Well there may be but I couldn’t figure out a way to do it so I just used brute force and zapped them from the serialized string. If you find a better solution please let me know.

  11. Nuran Jetha says:

    Thanks for your help. There is a method called shouldSendXSIType() (returns boolean) in SerializationContext which tells you whether or not the XSI types are in the XML string, but I cant find a way to set this property. For now, I will use the same method you did. Thanks again.

  12. FB says:

    Hi …I went through this code and it was very useful but I would also want to know how to deserialize a xml into ais object. Any input on this will be greatly appreciated.

  13. bwithers says:

    Well it certainly seems reasonable that there would be an Axis generated method to do this. After all, that’s exactly what happens when a SOAP message is received and an Axis Bean is constructed to be passed to your handler code.

    Unfortunately it has been more than a year since I’ve looked at Axis and I just don’t know off the top of my head. If you figure it out please post your answer here to help other folks looking to do the same things.

    Thanks.

  14. Colin says:

    Bob – thanks for your example which helped me get started.

    I’m no axis expert, but it seems to me that axis likes serialising messages not objects, so you need to encapsulate the object in a soap message first.

    The example supplied with axis is also helpful – and has both serialization and deserialization:

    axis-1_4\samples\encoding\TestSer.java

  15. bwithers says:

    Thanks Colin, glad it was helpful.

    You’ve lost me on the object vs message reference. The thing we are serializing is an object created by Axis which represents a message. It would be unreasonable to expect Axis to take an arbitrary object and serialize it to XML.

  16. Varun says:

    Hey that is really cool man…..I was trying to do it otherway around…using Castor with Axis…bt I found for that we should have to import the whole xsd schema into the wsdl. I mean there should be xsd file. But this thing will work irrespective to import xsd or not.

  17. Gopal says:

    Thanks a lot ppl for the help.

  18. ThomasD says:

    Hi,

    since this site helped me in the first place serializing an Axis object, I’d like to post a, well, not so clean but working way for deserializing an Axis object from an xml string:


    import org.apache.axis.Message;

    public class AxisObjectDeserializer {

    private static final String SOAP_START = "";
    private static final String SOAP_START_XSI = "";
    private static final String SOAP_END = "";

    public static Object deserializeObject(String xml, Class clazz) throws AxisObjectException {
    assert xml != null : "xml != null";
    assert clazz != null : "clazz != null";

    Object result = null;
    try {
    Message message = new Message(SOAP_START + xml + SOAP_END);
    result = message.getSOAPEnvelope().getFirstBody().getObjectValue(clazz);
    }
    catch (Exception e) {
    // most likely namespace error due to removed namespaces
    try {
    Message message = new Message(SOAP_START_XSI + xml + SOAP_END);
    result = message.getSOAPEnvelope().getFirstBody().getObjectValue(clazz);
    }
    catch (Exception e1) {
    // explicitly throw exception
    throw new AxisObjectException(e1);
    }
    }
    return result;
    }

    }

    Just try this with an object serialized with the class AxisObjectUtil. Took a little time and a lot of debugging to find this way of derserializing. Hope this helps!

    Regards
    Thomas

    • Brian Watson says:

      Thanks, Thomas. Four years after you wrote this comment, this is still the only helpful documentation I could find online for how to de-serialize XML using the stub classes that Axis generates. I’m so happy to have found this.

  19. ThomasD says:

    Sorry, the constants where not correctly formatted for HTML 🙂 Here they are:


    SOAP_START = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Header /><soapenv:Body>";
    SOAP_START_XSI = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><soapenv:Header /><soapenv:Body>";
    SOAP_END = "</soapenv:Body></soapenv:Envelope>";

  20. ThomasD says:

    And the corresponding Unit-Test shows how to use it. I renamed the AxisObjectUtil to AxisObjectSerializer in my code:


    import junit.framework.TestCase;
    import axisgenerated.PORRahmen;

    public class AxisObjectDeserializerTest extends TestCase {

    public void testAll() throws AxisObjectException {

    Integer orderId = 1234;

    PORRahmen order = new PORRahmen();
    order.setPORID(orderId);

    for (boolean removeNamespaces = true; removeNamespaces; removeNamespaces = false) {
    String serializedOrder = AxisObjectSerializer.serializeAxisObject(order, removeNamespaces, true);

    Object deserializeObject = AxisObjectDeserializer.deserializeObject(serializedOrder, PORRahmen.class);
    assertNotNull(deserializeObject);

    assertTrue(deserializeObject instanceof PORRahmen);
    PORRahmen deserializedOrder = (PORRahmen) deserializeObject;

    assertEquals(orderId, deserializedOrder.getPORID());
    }
    }

    }

  21. ThomasD says:

    Sorry to bother you all again, but it seems that removing the namespaces is a real problem for deserializing. I suppose you serialize it WITH the namespaces. Otherwise you will get errors when deserializing non simple types like arrays, embedded objects etc.

    Regards
    ThomasD

  22. Erik Wolf says:

    Hi Volks,

    Thanks for the code samples! They helped me a lot!
    Especially thanks for ThomasD’s code sample.
    I was totally shocked that in the axis documentation there was not any issu/sample
    regarding serialization/de-serialization at all… hmmm

    Thx,
    Erik

  23. Oleg says:

    Thank you very much for this sample, it is very useful! I spent many hours to find how to serialize.

  24. laszlolaszlo says:

    Hi, big thanks to all of you! I was trying to deserialize, but without your help i couldn’t manage it in 4 hours.. now it works! thank you!

  25. Raghunadh says:

    Am getting the following error when I tried creating an object from soap xml

    SimpleDeserializer encountered a child element, which is NOT expected, in something it was trying to deserialize

    I tried using the following code
    message.getSOAPEnvelope().getFirstBody().getObjectValue(
    clazz);
    It will be great if some can one help me on this ?

  26. cimgosa says:

    Thank you,

    it works in Axis 1.4 too. Normally i use the log4j appender for Axis:

    But when you cannot use log4j, this utility is useful.

  27. Rupesh Deshmukh says:

    This is one of the most valuable post related to apache axis serialization and deserialization.

  28. Pazis says:

    How to use “serialize” method of BeanSerializer class?

  29. mbausche says:

    Found a way to do that (works with axis2-1.5.5)

    QName qName = (QName) request.getClass().getField(“MY_QNAME”).get(request);

    OMElement requestOMElement = (OMElement) request.getClass().getMethod(“getOMElement”, QName.class, OMFactory.class).invoke(request, qName,OMAbstractFactory.getSOAP12Factory());

    String xmlString = requestOMElement.toStringWithConsume();

  30. if there are non-lating characters, the output is in html representation of the character (e.g. &#nnnn) . is there a way to avoid this and output in just unicode?

  31. saurabh says:

    This is not helping in case of enums….serialization and reverse.

  32. Elton Pignatel says:

    If your axis version not suport SerializationContextImpl try use SerializationContext, works fine.

  33. Nola says:

    This post, “Serializing an Axis JavaBean object to XML « Fuzzy Illogic” was terrific.
    I am making out a copy to present to my associates. Thanks for the post,Bobby

  34. jeth says:

    if it helps someone in the future, there is a much simpler way described here: http://coder.sonicpoets.com/?p=4

    basically its : new MessageElement(qName, object).getAsString();

  35. benzo says:

    thank you so much, it works wonders!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: