简体   繁体   中英

JAXB Unmarshalling: List of objects

I have

@XmlRootElement(namespace = "http://www.w3.org/2005/Atom", name = "content")
@XmlType(name = "course")
public class Course implements Resource

...

@XmlElementWrapper(name="subcourses")
@XmlElement(name="course")
List<Xlink> subcourses;         //!?

and Xlink class, which works fine in inline variable.

public class Xlink
{
    private String href;
    private String value;

    @XmlAttribute(namespace = "http://www.w3.org/1999/xlink")
    public String getHref()
    {
        return href;
    }

    public void setHref(String href)
    {
        this.href = href;
    }

    @XmlValue
    public String getValue()
    {
        return value;
    }

    public void setValue(String value)
    {
        this.value = value;
    }
}

for an XML input of

<atom:content atom:type="xml" xsi:type="course">
...
   <subcourses>
      <course xlink:href="course1">Some course</course>
      <course xlink:href="course2">other course</course>

And subcourses refuses to be unmarshalled (without any exception being thrown).

Note: MOXy is unfortunately not an option.

Edit: new marshalled object

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:content xmlns:ns2="http://www.w3.org/1999/xlink" xmlns:ns3="http://www.w3.org/2005/Atom">
    <code>SOME CODE</code>
    <name>name</name>
    <subcourses>
        <course ns2:href="Some href">some value</course>
        <course ns2:href="sdsdg">sdfhdfhdhdh</course>
    </subcourses>
</ns3:content>

Edit2: after some experimentation with unmarshalling and marshalling a test object I have discovered that I need to define xmlns namespace in the header of the content to match xlink:href= like xmlns:xlink="http://www.w3.org/1999/xlink" the problem is that I am getting the Course element from inside a wrapper class that is parsed out by resteasy. Thus the resulting class does not carry over the namespace information.

I somehow need to force JAXB to understand that xmlns:xlink="http://www.w3.org/1999/xlink" applies to the course elements, but after an hour of google I am at a loss.

Edit3: I am getting my object from

https://github.com/jirutka/atom-jaxb/blob/master/src/main/java/cz/jirutka/atom/jaxb/Entry.java

which is used on the server counterpart. Which in turn is a part of

https://github.com/jirutka/atom-jaxb/blob/master/src/main/java/cz/jirutka/atom/jaxb/Feed.java

Relevant parts of my unmarshalling code are:

Feed f = r.readEntity(Feed.class);
out.addAll(unmarshaller.Unmarshal(f.getEntries(), clazz));

where r is a javax.ws.rs.core.Response . And the unmarshaller

public List<T> Unmarshal(List<Entry> entries, Class clazz)
{
    List<T> out = new ArrayList<T>();
    T instance;
    for (Entry e : entries)
    {
        try
        {
            JAXBContext context = JAXBContext.newInstance(clazz);
            Unmarshaller unmarsh = context.createUnmarshaller();
            instance = (T) unmarsh.unmarshal((Node) e.getContent());

Since this is my first tangle with this technology, it is entirely possible that this code is 'wtf'.

When you annotate the field (instance variable) be sure to put @XmlAccessorType(XmlAccessType.FIELD) on your class.

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

@XmlRootElement(namespace = "http://www.w3.org/2005/Atom", name = "content")
@XmlAccessorType(XmlAccessType.FIELD)
public class Course implements Resource {

    @XmlElementWrapper(name = "subcourses")
    @XmlElement(name = "course")
    List<Xlink> subcourses;

}

Then make sure your XML input is correctly namespace qualified. Your input document should look something like the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<atom:content xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:atom="http://www.w3.org/2005/Atom">
    <code>SOME CODE</code>
    <name>name</name>
    <subcourses>
        <course xlink:href="Some href">some value</course>
        <course xlink:href="sdsdg">sdfhdfhdhdh</course>
    </subcourses>
</atom:content>

With my updated Course class, your Xlink class and a properly namespace qualified XML document the following demo code worked perfectly for me.

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

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum17766166/input.xml");
        Course course = (Course) unmarshaller.unmarshal(xml);

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

}

UPDATE #1

Edit2: after some experimentation with unmarshalling and marshalling a test object I have discovered that I need to define xmlns namespace in the header of the content to match xlink:href= like xmlns:xlink="http://www.w3.org/1999/xlink" the problem is that I am getting the Course element from inside a wrapper class that is parsed out by resteasy. Thus the resulting class does not carry over the namespace information.

The best place to fix your issue is where you extract the fragment you wish to unmarhal. Below is a strategy you can use with StAX.

input.xml

Below is a sample XML document where the namespace information is defined above the fragment you wish to unmarshal.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<foo xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:atom="http://www.w3.org/2005/Atom">
    <bar>
        <atom:content>
            <code>SOME CODE</code>
            <name>name</name>
            <subcourses>
                <course xlink:href="Some href">some value</course>
                <course xlink:href="sdsdg">sdfhdfhdhdh</course>
            </subcourses>
        </atom:content>
    </bar>
</foo>

Demo

Below we will use a StAX XMLStreamReader to navigate to the target fragment. We will have our JAXB implementation unmarshal this fragment. This way all the namespace information is preserved.

import javax.xml.bind.*;
import javax.xml.stream.*;
import javax.xml.transform.stream.StreamSource;

public class Demo {

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

        XMLInputFactory xif = XMLInputFactory.newFactory();
        StreamSource source = new StreamSource("src/forum17766166/input.xml");
        XMLStreamReader xsr = xif.createXMLStreamReader(source);
        while(xsr.hasNext()) {
            if(xsr.isStartElement() && "content".equals(xsr.getLocalName())) {
                break;
            }
            xsr.next();
        }

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Course course = (Course) unmarshaller.unmarshal(xsr);

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

}

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns3:content xmlns:ns2="http://www.w3.org/1999/xlink" xmlns:ns3="http://www.w3.org/2005/Atom">
    <subcourses>
        <course ns2:href="Some href">some value</course>
        <course ns2:href="sdsdg">sdfhdfhdhdh</course>
    </subcourses>
</ns3:content>

UPDATE #2

If you can't produce a better XML fragment as described in UPDATE #1, below is how you could fix the XML fragment you currently have.

NamespaceFilter

You could use a SAX XMLFilter to fix up your XML document.

import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class NamespaceFilter extends XMLFilterImpl {

    private static final String ATOM_URI = "http://www.w3.org/2005/Atom";
    private static final String XLINK_URI = "http://www.w3.org/1999/xlink";

    @Override
    public void startElement(String uri, String localName, String qName,
            Attributes atts) throws SAXException {
        if("atom:content".equals(qName)) {
            super.startElement(ATOM_URI, "content", qName, atts);
        } else if("course".equals(qName))  {
            AttributesImpl modifiedAtts = new AttributesImpl();
            modifiedAtts.addAttribute(XLINK_URI, "href", "xlink:href", null, atts.getValue(0));
            super.startElement(uri, localName, qName, modifiedAtts);
        } else {
            super.startElement(uri, localName, qName, atts);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        if("atom:content".equals(qName)) {
            super.endElement(ATOM_URI, "content", qName);
        } else {
            super.endElement(uri, localName, qName);
        }
    }

}

Demo

Below is how you can leverage the XmlFilter with JAXB:

import javax.xml.bind.*;
import javax.xml.parsers.*;
import org.xml.sax.*;

public class Demo {

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

        // Create the XMLFilter
        XMLFilter filter = new NamespaceFilter();

        // Set the parent XMLReader on the XMLFilter
        SAXParserFactory spf = SAXParserFactory.newInstance();
        SAXParser sp = spf.newSAXParser();
        XMLReader xr = sp.getXMLReader();
        filter.setParent(xr);

        // Set UnmarshallerHandler as ContentHandler on XMLFilter
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        UnmarshallerHandler unmarshallerHandler = unmarshaller
                .getUnmarshallerHandler();
        filter.setContentHandler(unmarshallerHandler);

        // Parse the XML
        InputSource xml = new InputSource("src/forum17766166/input.xml");
        filter.parse(xml);
        Course course = (Course) unmarshallerHandler.getResult();

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

}

For More Information


UPDATE #3

Below is a simplified version of your example code where everything works. Maybe there is something different in your code it will help you find.

Entry

import javax.xml.bind.annotation.*;

@XmlRootElement(namespace="http://www.w3.org/2005/Atom")
@XmlAccessorType(XmlAccessType.FIELD)
public class Entry<T> {

    @XmlElement(namespace = "http://www.w3.org/2005/Atom")
    @XmlSchemaType(name = "atomInlineOtherContent")
    private T content;

    public T getContent() {
        return content;
    }

}

input.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<atom:entry xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:atom="http://www.w3.org/2005/Atom">
    <atom:content>
        <code>SOME CODE</code>
        <name>name</name>
        <subcourses>
            <course xlink:href="Some href">some value</course>
            <course xlink:href="sdsdg">sdfhdfhdhdh</course>
        </subcourses>
    </atom:content>
</atom:entry>

Demo

import java.io.File;
import javax.xml.bind.*;
import org.w3c.dom.Node;

public class Demo {

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

        // Unmarshal Entry
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum17766166/input.xml");
        Entry entry = (Entry) unmarshaller.unmarshal(xml);

        // Unmarshal Course
        Node contentNode = (Node) entry.getContent();
        Course course = (Course) unmarshaller.unmarshal(contentNode);

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

}

Output

<?xml version="1.0" encoding="UTF-8"?>
<ns0:content xmlns:ns1="http://www.w3.org/1999/xlink" xmlns:ns0="http://www.w3.org/2005/Atom">
   <subcourses>
      <course ns1:href="Some href">some value</course>
      <course ns1:href="sdsdg">sdfhdfhdhdh</course>
   </subcourses>
</ns0:content>

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