简体   繁体   中英

JAXB @XmlAdapter for arbitrary XML

I have a org.w3c.dom.Element that I'm returning from my XmlAdapter for a custom @XmlElement and I'd like to include it as part of a JAXB object as arbitrary XML (I'm aware I'll have to hand-craft the XSD). However, JAXB complains with

org.w3c.dom.Element is an interface, and JAXB can't handle interfaces.

Apparently the w3c XML types are not supported as Java types , which is a shame. But further than this, I get the same error when I use javax.xml.transform.Result which is apparently supported.

How can I include arbitrary XML elements as elements in JAXB?

Note: as per https://forums.oracle.com/thread/1668210 I've also tried

MessageFactory factory = MessageFactory.newInstance();
message = factory.createMessage();          
SOAPElement element = message.getSOAPBody().addDocument(doc);

but that is also giving the same error.

TL;DR

You can have an XmlAdapter that converts you domain object to an instance of org.w3c.dom.Element as long as you specify the value type as Object (not Element ).


Below is a full example.

XmlAdapter

A field/property of type java.lang.Object will keep unknown content as DOM nodes. You can leverage this in your use case by specifying the value type in your XmlAdapter as Object . You will need to ensure that the root element returned from the marshal method matches the field/property as defined by the @XmlElement annotation.

import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.parsers.*;
import org.w3c.dom.*;

public class BarAdapter extends XmlAdapter<Object, Bar>{

    private DocumentBuilder documentBuilder;

    public BarAdapter() {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            documentBuilder = dbf.newDocumentBuilder();
        } catch(Exception e) {
            // TODO - Handle Exception
        }
    }

    @Override
    public Bar unmarshal(Object v) throws Exception {
        Bar bar = new Bar();
        Element element = (Element) v;
        bar.value = element.getTextContent();
        return bar;
    }

    @Override
    public Object marshal(Bar v) throws Exception {
        Document document = documentBuilder.newDocument();
        Element root = document.createElement("bar");
        root.setTextContent(v.value);
        return root;
    }

}

Java Model

Foo

The @XmlJavaTypeAdapter annotation is used to reference the XmlAdapter .

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {

    @XmlJavaTypeAdapter(BarAdapter.class)
    private Bar bar;

}

Bar

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Bar {

    String value;

}

Demo Code

Demo

Since there is a cost to creating the DocumentBuilderFactory we can leverage JAXB's ability to handle stateful instances of XmlAdapter by setting an instance on the Marshaller.

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

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum18272059/input.xml");
        Foo foo = (Foo) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setAdapter(new BarAdapter());
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(foo, System.out);
    }

}

input.xml/Output

<?xml version="1.0" encoding="UTF-8"?>
<foo>
    <bar>Hello World</bar>
</foo>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM