Friday, August 31, 2012

JAXB - marshalling an entity with missing @XmlRootElement


I'm currently working on an application that needs to send/receive SOAP messages which are fairly large. With simple cases, I could simply use SoapUI to load the wsdl and execute tests for each of the operations defined for that service. But when it comes to actually invoking the service via application, things could becomes little bit murkier if you start getting SOAP faults instead of valid response.
I ran into such scenario and looking at the java code, everything looked OK. Next thing was to print out the xml payload to see whats going on.

To do that, I had to marshal the java object into xml using JAXBContext and pass it to a writer. Following code does that..


        JAXBContext jaxbContext;
        String xmlData = "";
        String type = obj.getClass().getName();
        try {
            StringWriter writer = new StringWriter();
            jaxbContext = JAXBContext.newInstance(obj.getClass());
            Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            jaxbMarshaller.marshal(obj, writer);
            xmlData = writer.toString();
            logger.debug(String.format("XML Representation of %s: \n%s", type, xmlData));
        } catch (JAXBException e) {
            logger.error(String.format("Unable to marshal %s: \n%s", type, e.getMessage()));
        }


But when I ran it, I got the following error:

javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: unable to marshal type "com.company.model.Header" as an element because it is missing an @XmlRootElement annotation]

It was easily fixed by adding @XmlRootElement annotation to those entities that I was trying to marshal, but that wasnt a good solution, since I really shouldn't have to do that.
These entities were generated via Apache CXF's wsdl2java task and every time I run that, its going to overwrite the changes that I did. After some googling, found that there were two ways that a JAXB runtime can perform marshaling:
  1. By adding @XmlRootElement
  2. Using JAXBElement wrapper objects, where T is the type that you'd want to marshal. 

Second option was definitely the way for me to go here.. Made the appropriate change and voila! I got what I wanted w/o having to worry about manipulating the wsdl generated code. :)


        JAXBContext jaxbContext;
        String xmlData = "";
        try {
            StringWriter writer = new StringWriter();
            JAXBElement<Header> jaxbWrappedHeader =  objectFactory.createHeader(obj);
            jaxbContext = JAXBContext.newInstance(Header.class);
            Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
            jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            jaxbMarshaller.marshal(jaxbWrappedHeader, writer);
            xmlData = writer.toString();
            logger.debug(String.format("XML Representation of Header: \n%s", xmlData));
        } catch (JAXBException e) {
            logger.error(String.format("Unable to marshal Header: \n%s", e));
        }

Only downside to this was not being able to use generics..



No comments: