Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB: how to append sub-tree to an object and create a full tree

Tags:

java

xml

jaxb

I have one sub-tree that I would like to append on an object and make JAXB marshall the all thing as a single tree (and with appropriate tags). But currently, the root tag of the sub-tree is replaced by the tag of another object

Unfortunately, I am not allowed to publish the original code here, so I reproduced my problem in a test code (so bear with me if you find this dumb).

The idea is that I would like to output the following structure:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root xmlns:ns2="urn:my:foo:bar:1.0" xmlns:ns3="urn:other:foo:bar:1.1">
    <Content>
        <Header>
            <ns3:Leaf/>
        </Header>
    </Content>
</ns2:Root>

but currently, all I get is this:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root xmlns:ns2="urn:my:foo:bar:1.0" xmlns:ns3="urn:other:foo:bar:1.1">
    <Content>
        <Header/>
    </Content>
</ns2:Root>

I have two XSD's to generate all the necessary classes, so I am ok on that side (but since those classes are generated, I cannot modify them).

Here is a sample code that produces the second XML (the wrong one):

package foo.bar;

import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

public class Test {

    private JAXBContext context;

    public Test() throws JAXBException {
        context = JAXBContext.newInstance(RootElement.class, LeafElement.class);
    }

    @XmlRootElement(name = "Root", namespace = "urn:my:foo:bar:1.0")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "Root", propOrder = { "content" })
    public static class RootElement {
        @XmlElement(name = "Content")
        protected ContentElement content;

        public ContentElement getContent() {
            return content;
        }

        public void setContent(ContentElement content) {
            this.content = content;
        }
    }

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "Content", propOrder = { "dummy" })
    public static class ContentElement {
        @XmlElement(name = "Header")
        protected Object dummy;

        public Object getDummy() {
            return dummy;
        }

        public void setDummy(Object dummy) {
            this.dummy = dummy;
        }
    }

    @XmlRootElement(name = "Leaf", namespace = "urn:other:foo:bar:1.1")
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "Leaf")
    public static class LeafElement {

    }

    public Node marshal(Object obj) throws JAXBException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = null;
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.newDocument();
        } catch (ParserConfigurationException ex) {
            throw new JAXBException(ex);
        }

        Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        m.marshal(obj, doc);
        return doc.getDocumentElement();
    }

    public void marshal(Object obj, OutputStream stream) throws JAXBException {
        Marshaller m = context.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
        m.marshal(obj, stream);
    }

    public void test() throws JAXBException {
        RootElement root = new RootElement();
        ContentElement content = new ContentElement();
        root.setContent(content);
        LeafElement leaf = new LeafElement();
        content.setDummy(marshal(leaf));
        marshal(root, System.out);
    }

    public static void main(String[] args) throws JAXBException {
        new Test().test();
    }

}

In that code you find 3 "marshallable" classes:

  1. RootElement,
  2. ContentElement and
  3. LeafElement.

The first two classes come from one XSD (with a given namespace) and the last one comes from another XSD (with another namespace), as illustrated in the sample code.

So far, all I found to fix this, was to create an additional class that would be set as dummy on the ContentElement and would itself hold the LeafElement, so that JAXB creates the appropriate intermdiate Node. But I find this solution quite ugly, not really maintainable and was hoping that JAXB had some way to handle such cases.

If you need more info, or you need me to re-formulate my question, don't hesitate. I am having a hard time to explain my problem with simple words.

Constraints are the following:

  • I cannot modify RootElement, ContentElement nor LeafElement
  • I cannot use something else than JAXB
like image 804
Guillaume Polet Avatar asked Nov 04 '22 16:11

Guillaume Polet


1 Answers

If you cannot change any element class, then you must create a holder object to leaf.

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class LeafElementHolder {
    @XmlAnyElement
    private Object leaf;

    public Object getLeaf() {
        return leaf;
    }

    public void setLeaf(Object leaf) {
        this.leaf = leaf;
    }
}

Add this class to context

public Test() throws JAXBException {
    context = JAXBContext.newInstance(RootElement.class, LeafElement.class, LeafElementHolder.class);
}

and use this in your test() method

    LeafElement leaf = new LeafElement();
    LeafElementHolder holder = new LeafElementHolder();
    holder.setLeaf(leaf);
    content.setDummy(marshal(holder));

You have 4 elements in XML so you must have 4 classes in Java.

ns2:Root
  ns2:Content
    ns2:Header
      ns3:Leaf
like image 105
viktor.balazs Avatar answered Nov 14 '22 23:11

viktor.balazs