4

I have two XML files

tree.xml

<tree>
    <xi:include href="fruit.xml" xmlns:xi="http://www.w3.org/2001/XInclude">
        <xi:fallback>
            <fruit/>
        </xi:fallback>
    </xi:include>
</tree>

fruit.xml

<fruit>
     ...
</fruit>

I inherited the code to unmarshall the files, and it returns a single java object. I now need to marshall the single java object back to two files. I realize there are other solutions (i.e. using two objects instead of one, which is an option) but I need to know if its possible to marshall a single object and maintain the xi:include (or reintroduce it) and export to two (or more) xml files.

Is this even possible? If so any tips/ideas?

Thanks

Updates:

I have been researching this (I researched prior to asking). I did find this tutorial http://tutorial.waycoolsearch.com/java/jaxb2.php which appeared to have my answer, but alas when I marshall the file it takes what was two and makes one.

marbletravis
  • 135
  • 1
  • 8

2 Answers2

4

Below I'll demonstrate how an XmlAdapter could be used to support both marshalling and unmarshalling for this use case.

XmlAdapter (IncludeFruitAdapter)

An XmlAdapter could be used for this use case.

import java.io.File;
import javax.xml.bind.*;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;

public class IncludeFruitAdapter extends XmlAdapter<IncludeFruitAdapter.Include, Fruit> {

    private JAXBContext jc;
    private String href = "fruit.xml";

    public IncludeFruitAdapter() {
        try {
            jc = JAXBContext.newInstance(Fruit.class);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }


    public static class Include {

        @XmlAttribute
        public String href;

        @XmlElement(namespace="http://www.w3.org/2001/XInclude")
        public Fallback fallback;

    }

    public static class Fallback {

        @XmlElementRef
        public Fruit value;

    }

    @Override
    public Include marshal(Fruit fruit) throws Exception {
        File xml = new File(href);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(fruit, xml);

        Include include = new Include();
        include.href = href;
        include.fallback = new Fallback();
        include.fallback.value = new Fruit();
        return include;
    }

    @Override
    public Fruit unmarshal(Include include) throws Exception {
        File xml = new File(include.href);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        try {
            return (Fruit) unmarshaller.unmarshal(xml);
        } catch(Exception e) {
            return include.fallback.value;
        }
    }

}

Tree

The @XmlJavaTypeAdapter is used to reference the XmlAdapter.

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

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

    @XmlJavaTypeAdapter(IncludeFruitAdapter.class)
    @XmlElement(name="include", namespace="http://www.w3.org/2001/XInclude")
    private Fruit fruit;

}

Fruit

import javax.xml.bind.annotation.*;

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

    private String name;

}

Demo

Below is some demo code you can run to prove that everything works:

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

public class Demo {

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

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("input.xml");
        Tree tree = (Tree) unmarshaller.unmarshal(xml);

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

    }

}
bdoughan
  • 147,609
  • 23
  • 300
  • 400
0

I figured out an answer, at least a work around. I suppose this may be the way to do it.

I was hoping that JAXB had some kind of method to define a section of an object as an include for marshaling, but I could never find anything.

Note: I have one extra hack that I will explain below, I suppose you could do it without but I couldn't get it to work and in the case where someone else needs to do this at least they will know.

The flow is as follows.

  1. One XML file with XI:Include gets unmarshalled to a java object. This object contains the contents of the 2 files (the parent and include).
  2. The object is updated.
  3. The object is marshalled back to a single temp file (this is the hack).
  4. The single temp file is umarshalled to a DOM document. (I wanted to marshall straight to a DOM document, but I always got an empty DOM document with no exceptions).
  5. I then manipulate the DOM document by first removing the node that needs to go into the XI:Include and then insert a new XI:include file.
  6. Transform the DOM document into an xml file. Note, in my case the content of the XI:include won't change, but I suppose that node could be put in a new document and transformed.

I will update with code shortly.

Any other ways to solve this?

Thanks, Travis

marbletravis
  • 135
  • 1
  • 8