Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to programmatically configure JAXB?

Tags:

java

jaxb

Say I have two JavaBeans Person and Address.

If I create a list of Person objects, I'd like to marshal to something like this:

<persons>
  <person>...</person>
</persons>

It's possible to use the technique described here: Using JAXB to unmarshal/marshal a List<String>

By annotating JaxbList with @XmlRootElement(name = "persons") and @XmlElement(name = "person"), then it's possible to marshal to the XML above.

But, it'd be nice to be able to reuse the same JaxbList<T> class to also marshal a list of Address objects. And in reality, I will have many other types of beans. I can go with something like:

<list>
   <item xsi:type="person" xmlns:xsi="http://www.w2.org/2001/XmlSchema-instance"></item>
</list>

But, ideally, it'd be nice to have it replace "list" with the plural version of class name and "item" with the class name.

So, is it possible to programmatically configure the JaxbContext or something during runtime and essentially set the value of the name inside @XmlRootElement and @XmlElement?

Or any other way to get this working without having to write a separate implementation of JaxbList for every bean type? Maybe XmlJavaTypeAdapter can achieve this sort of thing?

Update @Blaise Doughan's solution accepted below works great. For my use case, I needed to go straight from Java object to XML, here's what worked (note this is not my full implementation, it's sort of just pseudo code for demonstration):

    //JAXBContext is thread safe and so create it in constructor or 
    //setter or wherever:
    ... 
    JAXBContext jc = JAXBContext.newInstance(Wrapper.class, clazz);
    ... 

    public String marshal(List<T> things, Class clazz) {

      //configure JAXB and marshaller     
      Marshaller m = jc.createMarshaller();
      m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

      //Create wrapper based on generic list of objects
      Wrapper<T> wrapper = new Wrapper<T>(things);
      JAXBElement<Wrapper> wrapperJAXBElement = new JAXBElement<Wrapper>(new QName(clazz.getSimpleName().toLowerCase()+"s"), Wrapper.class, wrapper);

      StringWriter result = new StringWriter();
      //marshal!
      m.marshal(wrapperJAXBElement, result);

      return result.toString();

    }
like image 513
Upgradingdave Avatar asked Nov 07 '12 15:11

Upgradingdave


People also ask

What can I use instead of JAXB?

XOM, JDOM, dom4j, etc. etc. Projects like Castor and Apache XMLBeans predate JAXB, so you could have a look at those. Ulf Dittmer wrote: XOM, JDOM, dom4j, etc.

Is JAXB an API?

The Java™ Architecture for XML Binding (JAXB) provides an API and tools that automate the mapping between XML documents and Java objects. The JAXB framework enables developers to perform the following operations: Unmarshal XML content into a Java representation.

Is JAXB memory efficient?

Generally JAXB is quite efficient and you shouldn't care about memory issues unless your application handles XMLs of very large size.

What is JAXB API used for?

A new Java API called Java Architecture for XML Binding (JAXB) can make it easier to access XML documents from applications written in the Java programming language.


1 Answers

You could create a generic Wrapper object like the following:

Wrapper

You could create a generic wrapper class with a List property annotated with @XmlAnyElement(lax=true). The type of the object used to populate this list will be based on its root element (see: http://blog.bdoughan.com/2010/08/using-xmlanyelement-to-build-generic.html).

package forum13272288;

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

public class Wrapper<T> {

    private List<T> items = new ArrayList<T>();

    @XmlAnyElement(lax=true)
    public List<T> getItems() {
        return items;
    }

}

Address

You will need to annotate the possible contents of the list with @XmlRootElement.

package forum13272288;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Address {

}

Person

package forum13272288;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Person {

}

Demo

The demo code below demonstrates how to use the Wrapper class. Since the root element can be different you will need to specify that you want to unmarshal to the wrapper class. Alternatively you could leverage the @XmlElementDecl annotation to associate multiple root elements with the wrapper class (see: http://blog.bdoughan.com/2012/07/jaxb-and-root-elements.html).

package forum13272288;

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

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Wrapper.class, Person.class, Address.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        StreamSource personsXML = new StreamSource("src/forum13272288/persons.xml");
        JAXBElement<Wrapper> wrapper1 = unmarshaller.unmarshal(personsXML, Wrapper.class);
        marshaller.marshal(wrapper1, System.out);

        StreamSource addressesXML = new StreamSource("src/forum13272288/addresses.xml");
        JAXBElement<Wrapper> wrapper2 = unmarshaller.unmarshal(addressesXML, Wrapper.class);
        marshaller.marshal(wrapper2, System.out);
    }

}

Output

Below is the output from running the demo code. The files persons.xml and addresses.xml look just like there corresponding output.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persons>
    <person/>
    <person/>
</persons>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addresses>
    <address/>
    <address/>
</addresses>

For More Information

  • http://blog.bdoughan.com/2012/11/creating-generic-list-wrapper-in-jaxb.html
like image 95
bdoughan Avatar answered Oct 14 '22 11:10

bdoughan