Serializing with JAXB and the Any
Asked Answered
D

3

12

I have a schema that defines the following type:

<xsd:complexType name="Payload">
   <xsd:sequence>
      <xsd:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
   </xsd:sequence>
</xsd:complexType>

And that creates an object like so:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Payload", propOrder = {
    "any"
})
public class Payload {

    @XmlAnyElement(lax = true)
    protected List<Object> any;
}

Now I try adding another generated JAXB object to that Payload doing something like this:

Class payloadClass = ...;
JAXBContext context = JAXBContext.newInstance( WrapperRequest.class, payloadClass);
...
marshaller.marshal( wrappedRequest );

But I get a terrible exception that looks like it'll never work so I decide to serialize the payload object to XML first then add that as a string in the payload.

StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance( sdoRequest.getClass() );
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(new JAXBElement(new QName("uri", sdoRequest.getClass().getSimpleName()), sdoRequest.getClass(), sdoRequest), writer);
payload.getAny().add( writer.toString() );

And this blows up with an exception saying "java.lang.String" does not contain an @XmlRootElement.

So how will the use of xs:any ever work with JAXB? Nothing seems to want to work because JAXB turns the Payload into Object, and it won't serialize just anything in Object. This is all inside Axis2 as well so it's been very challenging to get to this point.

Dahlia answered 18/12, 2012 at 21:25 Comment(5)
What exception do you get when you try to add the wrapped object to the Any payload?Creator
The following should help: blog.bdoughan.com/2010/08/…Struma
This is partly how the WSDLs are put together which I have zero control over. But, the wrapping request is defined in one WSDL and the request going in the payload is defined in another WSDL. They both declare a BaseRequest, but when the code is generated they are different classes. And I get this exception Two classes have the same XML type name {esp.lala.com/2012/eo}BaseRequest. Use @ XmlType.name and @ XmlType.namespace to assign different names to them.Dahlia
@BlaiseDoughan The objected being added to the payload isn't annotated with @ XmlRootElement because it's generated from the WSDL. What should I do? What about the error I added? See any easy way around that?Dahlia
+one for "I get a terrible exception that looks like it'll never work"Arianaariane
S
19

Below I will demonstrate JAXB (JSR-222) and any with an example:

Payload

The any property is annotated with @XmlAnyElement(lax=true). This means that for that property if an element is associated with a class via @XmlRootElement or @XmlElementDecl then an instance of the corresponding object will be used to populate the property if not the element will be set as an instance of org.w3c.dom.Element.

package forum13941747;

import java.util.List;
import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "Payload", propOrder = {
    "any"
})
public class Payload {

    @XmlAnyElement(lax = true)
    protected List<Object> any;

}

Foo

Below is an example of a class annotated with @XmlRootElement.

package forum13941747;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Foo {

}

Bar

Below is an example of a class without the @XmlRootElement annotation. In this use case we will leverage the @XmlElementDecl annotation on a factory class (usually called ObjectFactory) annotated with @XmlRegistry.

package forum13941747;

public class Bar {

}

ObjectFactory

Below is an example of specifying an @XmlElementDecl annotation for the Bar class.

package forum13941747;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;

@XmlRegistry
public class ObjectFactory {

    @XmlElementDecl(name="bar")
    public JAXBElement<Bar> createBar(Bar bar) {
        return new JAXBElement<Bar>(new QName("bar"), Bar.class, bar);
    }

}

input.xml

Below is the input document we'll use for this example. There are 3 elements that correspond to the any property. The first corresponds to the @XmlRootElement annotation on the Foo class. The second corresponds to the @XmlElementDecl annotation for the Bar class and the third does not correspond to any of the domain classes.

<?xml version="1.0" encoding="UTF-8"?>
<payload>
    <foo/>
    <bar/>
    <other/>
</payload>

Demo

In the demo code below we will unmarshal the input document, then output the classes of the objects in the resulting any property and then marshal the payload object back to XML.

package forum13941747;

import java.io.File;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Payload.class, Foo.class, ObjectFactory.class);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum13941747/input.xml");
        Payload payload = (Payload) unmarshaller.unmarshal(xml);

        for(Object o : payload.any) {
            System.out.println(o.getClass());
        }

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(payload, System.out);
    }

}

Output

Below is the output from running the demo code. Note the classes corresponding to the objects in the any property. The foo element became an instance of the Foo class. The bar element became an instance of JAXBElement that holds an instance of Bar. The other element became an instance of org.w3c.dom.Element.

class forum13941747.Foo
class javax.xml.bind.JAXBElement
class com.sun.org.apache.xerces.internal.dom.ElementNSImpl
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<payload>
    <foo/>
    <bar/>
    <other/>
</payload>
Struma answered 19/12, 2012 at 11:9 Comment(0)
C
1

Make use of Object Factory for mashelling the object like below you no need to have @XmlRootElement in DemoType.java .,

DemoType demoServiceRequest = new DemoType();
ObjectFactory obDemo = new ObjectFactory();  
Request requestObject = new Request();     
requestObject.setAny(obDemo.createDemo(demoServiceRequest));

And add DemoType class at Request.java like @XmlSeeAlso({DemoType.class})

Crosslink answered 9/6, 2015 at 22:25 Comment(0)
T
0

Should your payload be a XML string, I managed to solve the very same problem using the code below:

import javax.xml.parsers.DocumentBuilderFactory;

//...

String XMLPAYLOAD = "...";

Payload payload = new ObjectFactory().createPayload();
try {
    payload.setAny(DocumentBuilderFactory
           .newInstance()
           .newDocumentBuilder()
           .parse(new InputSource(new StringReader(XMLPAYLOAD)))
           .getDocumentElement());
} catch (Exception e) {
    e.printStackTrace();
}

//...
Taratarabar answered 23/5, 2019 at 16:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.