Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAXB marshalling purely from interfaces

Tags:

java

jaxb

jax-rs

I have a complex hierarchy of Java interfaces that I'd like to marshal (and not necessarily unmarshal) with JAXB. These interfaces represent objects that will be returned from a JAX-RS REST API as XML, JSON, YAML, etc. (I'm using RestEasy, which can marshal JAXB-annotated types in formats other than XML.)

The problem seems to be that JAXB is fundamentally class-oriented. I've done lots of web research on the difficulties with JAXB and interfaces, the closest solutions being MOXy JAXB - Map Interfaces to XML and JAXB and Interface Fronted Models. However, I have two main issues: a) I want to annotate/work in terms of interfaces, not the concrete classes (of which there will be multiple implementations and containing significant other state that should not be marshaled), and b) I have multiple levels of interface inheritance. Here's a sample of the interfaces, minus any JAXB annotations so far:

interface Uuided {
  UUID getId();
}
interface Named {
  String getName();
}
interface Component extends Uuided, Named {
  Map<String, ComponentAttribute> getAttributes();
}
interface Attribute extends Named {
  Type getType();
  Object getValue();
}
interface ComponentAttribute extends Attribute {
  Component getDeclaringComponent();
}

Ideally, this would generate something like:

<component id="xxx" name="thing">
  <attributes>
    <componentAttribute name="color">
      <type><stringType/></type>
      <value>green</value>
      <declaringComponent idref="xxx"/>
    </componentAttribute>
  </attributes>
</component>

Clearly, in the abstract, this leads to issues like determining the most derived annotated interface, of which there could theoretically be more than one. In my case, however, I believe the concrete classes only implement a single interface that should be marshaled. Unmarshaling is not necessary, as I have separate classes defining upsert properties.

So my question is, is this even possible to handle with JAXB, and if so, how? Even if I have to be very explicit in defining bindings, adapters, etc., I'd like to work within the JAXB framework to get the benefit of all the non-XML providers in RestEasy.

like image 310
Trevor Robinson Avatar asked Mar 06 '12 18:03

Trevor Robinson


2 Answers

Short answer: use @XmlElement(type = Object.class) on your interface field.

Details below:

I have found 2 ways in which you could make JAXB serialize your interfaces:

  1. @XmlAnyElement
  2. @XmlElement(type = Object.class)

1.@XmlAnyElement

Simply annotate your interface type field with @XmlAnyElement and JAXB will serialize the interface from it's concrete type. Don't forget to annotate the concrete types with @XmlRootElement and to add the concrete types to the JAXBContext. Full example follows:

public class InterfaceSerializer {

    @XmlRootElement
    public static class Pojo {
        Pojo() {
            field1 = new PojoFieldImpl1();
            field2 = new PojoFieldImpl2();
            field3 = new PojoFieldImpl1();
        }

        @XmlAnyElement
        public IPojoField field1;
        @XmlAnyElement
        public IPojoField field2;
        @XmlAnyElement
        public IPojoField field3;

        @Override
        public String toString() {
            return "field1 = " + field1 + "\nfield2 = " + field2 + "\nfield3 = " + field3;
        }
    }

    public static interface IPojoField {

    }

    @XmlRootElement
    public static class PojoFieldImpl1 implements IPojoField {

        PojoFieldImpl1() {
            value = "PojoFieldImpl1 value";
        }

        public String value;

        @Override
        public String toString() {
            return value;
        }
    }

    @XmlRootElement
    public static class PojoFieldImpl2  implements IPojoField {

        PojoFieldImpl2() {
            value = "PojoFieldImpl2 value1";
            value2 = "PojoFieldImpl2 value2";
        }

        public String value;
        public String value2;

        @Override
        public String toString() {
            return value + " " + value2;
        }
    }

    public static void main(String []args) throws JAXBException {
        Pojo pojo = new Pojo();
        JAXBContext jaxbContext = JAXBContext.newInstance(Pojo.class, PojoFieldImpl1.class, PojoFieldImpl2.class);
        Marshaller marshaller = jaxbContext.createMarshaller();

        marshaller.marshal(pojo, new File("interfaceSerializer.xml"));
    }
}

Output XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
    <pojoFieldImpl1>
        <value>PojoFieldImpl1 value</value>
    </pojoFieldImpl1>
    <pojoFieldImpl2>
        <value>PojoFieldImpl2 value1</value>
        <value2>PojoFieldImpl2 value2</value2>
    </pojoFieldImpl2>
    <pojoFieldImpl1>
        <value>PojoFieldImpl1 value</value>
    </pojoFieldImpl1>
</pojo>

The downsides of this method:

  • you can't distinguish from XML each individual field from your pojo (same implementations will be written with the same tag)
  • you don't have any type information to Unmarshall your XML (if you would wish to do so)

These downsides are fixed in the second solution:

2.@XmlElement(type = Object.class)

I've stumbled upon this in mikesir87's blog post. Simply replace the @XmlAnyElement annotations from above with @XmlElement(type = Object.class) You should have something like this in the Pojo class from above:

@XmlElement(type = Object.class)
public IPojoField field1;
@XmlElement(type = Object.class)
public IPojoField field2;
@XmlElement(type = Object.class)
public IPojoField field3;

Re-running our example, the resulting XML:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<pojo>
    <field1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
        <value>PojoFieldImpl1 value</value>
    </field1>
    <field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl2">
        <value>PojoFieldImpl2 value1</value>
        <value2>PojoFieldImpl2 value2</value2>
    </field2>
    <field3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="pojoFieldImpl1">
        <value>PojoFieldImpl1 value</value>
    </field3>
</pojo>

This can also be deserialized:

Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Pojo unmarshalledPojo = (Pojo) unmarshaller.unmarshal(new File("interfaceSerializer.xml"));  
System.out.println(unmarshalledPojo);

Resulting output:

field1 = PojoFieldImpl1 value
field2 = PojoFieldImpl2 value1 PojoFieldImpl2 value2
field3 = PojoFieldImpl1 value

Probably a "hackish" solution, but it gets the job done.

like image 199
alexsvecencu Avatar answered Sep 30 '22 23:09

alexsvecencu


I think the answer is that JAXB isn't at all intended to support this and that it's foolish to try to force it. Also, JAXB-driven JSON marshaling turns out not to be ideal either.

I ended up writing my own marshaling framework, with its own set of annotations:

@MarshalMixin // marshal fields but not a top-level object
interface Uuided {
  @MarshalAsString // ignore properties; just call toString()
  @MarshalId // treat as identifier for @MarshalUsingIds or cyclic ref
  UUID getId();
}
@MarshalMixin
interface Named {
  @MarshalId
  String getName();
}
@MarshalObject // top-level marshaled object providing class name
interface Component extends Uuided, Named {
  @MarshalAsKeyedObjectMap(key = "name") // see description below
  Map<String, ComponentAttribute> getAttributes();
}
@MarshalObject
interface Attribute extends Named {
  Type getType();
  @MarshalDynamic // use run-time (not declared) type
  Object getValue();
}
interface ComponentAttribute extends Attribute {
  @MarshalUsingIds
  Component getDeclaringComponent();
}

The generated marshalers write to an abstraction layer (currently implemented for JSON and XML). This gives a lot more flexibility to make the output natural for different representations without a ton of annotations and adapters. E.g., what I'm calling keyed-object maps, where each object contains its map key. In JSON, you want a map, but in XML, you want a sequence:

{..., map: {'a': {'name': 'a', ...}, 'b': {'name: 'b', ...}, ...}, ...}
...<map><thing name='a'>...</thing><thing name='b'>...</thing></map>...

Seems like as many as 4 other people care about this too, so hopefully I can open-source it eventually. :-)

like image 20
Trevor Robinson Avatar answered Sep 30 '22 23:09

Trevor Robinson