Steve Skrla
Steve Skrla

Reputation: 1720

JAXB Marshalling and Generics

I am trying to use JAXB's introspection to marshall and unmashall some existing domain objects marked up with JAXB annotations. Most things work as expected, but I am having quite a bit of trouble getting a fairly simple class to serialize. This class is used as an @XmlElement on a number of beans and looks something like:

public class Range<E extends Comparable<E>> implements Serializable {
    protected boolean startInclusive, endInclusive;
    protected E       start, end;

    public Range(){
            startInclusive = endInclusive = true;
    }

    public boolean contains(E value){...}

    public E getEnd() {
            return end;
    }

    public void setEnd(E end) {
            this.end = end;
    }

    public boolean isEndInclusive() {
            return endInclusive;
    }

    public void setEndInclusive(boolean endInclusive) {
            this.endInclusive = endInclusive;
    }

    public E getStart() {
            return start;
    }

    public void setStart(E start) {
            this.start = start;
    }

    public boolean isStartInclusive() {
            return startInclusive;
    }

    public void setStartInclusive(boolean startInclusive) {
            this.startInclusive = startInclusive;
    }
}

I have tried to do the following, with no success, JAXB is still angry with the interface Comparable.

public class DoubleRange extends Range<Double> {}

Using both Range and DoubleRange as return types for the bean getter's yields an exception like:

java.lang.Comparable is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at java.lang.Comparable
        at protected java.lang.Comparable com.controlpath.util.Range.start
        at example.util.Range
        at example.util.DoubleRange
        at public example.util.DoubleRange example.domain.SomeBean.getRange()
        at example.domain.SomeBean

I realize that in most cases List<T> and Map<T, U> only work because the JAXB specification has special provisions for those types when they are encountered on beans, but is there any way to convey what I want to the JAXB introspection engine without having to reimplement range with non-generic fields?

Upvotes: 10

Views: 20596

Answers (6)

Daniel Szabo
Daniel Szabo

Reputation: 11

What I did was the following:

  1. Create an empty class and mark it for JAXB
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Marshallable {}
  1. Make every class that will end up in the generic class as type extend this Marshallable type
(...)
public class A extends Marshallable {
(...)
  1. Constrain your generic class to only allow subclasses of Marshallable as generic type
public class GenericType<T extends Marshallable> {
  1. Use the class as you were going to originally
A a = new A();

jaxbMarshaller.marshal(new GenericType(a), outputStream);

And now Jaxb will work with every type that extends Marshallable, even as part of a generic type.

Upvotes: 0

ivan_ivanovich_ivanoff
ivan_ivanovich_ivanoff

Reputation: 19453

How about

public class Range<**E extends Number**> implements Serializable { ...
  • Number is a class

  • I bet JAXB knows default marshalling/unmarshalling rules for Number

For unmarshalling to specific type, you need XmlAdapter as I described here: JAXB inheritance, unmarshal to subclass of marshaled class

Upvotes: 1

StaxMan
StaxMan

Reputation: 116512

Actually, it is not quite clear to me why this would not work. It seems like JAXB should be able to resolve specific subtype correctly: if (and only if!) this type is NOT the root type (which it is not as per your description). I mean, it is just a Bean; so if bean with T replaced with direct type works, so should generic version iff using sub-classing to bind types (as is done in example).

So perhaps it could be a bug in implementation?

Upvotes: 0

ivan_ivanovich_ivanoff
ivan_ivanovich_ivanoff

Reputation: 19453

You could write a custom adapter (not using JAXB's XmlAdapter) by doing the following:

1) declare a class which accepts all kinds of elements and has JAXB annotations and handles them as you wish (in my example I convert everything to String)

@YourJAXBAnnotationsGoHere
public class MyAdapter{

  @XmlElement // or @XmlAttribute if you wish
  private String content;

  public MyAdapter(Object input){
    if(input instanceof String){
      content = (String)input;
    }else if(input instanceof YourFavoriteClass){
      content = ((YourFavoriteClass)input).convertSomehowToString();
    }else if(input instanceof .....){
      content = ((.....)input).convertSomehowToString();
    // and so on
    }else{
      content = input.toString();
    }
  }
}

// I would suggest to use a Map<Class<?>,IMyObjToStringConverter> ...
// to avoid nasty if-else-instanceof things

2) use this class instead of E in your to-be-marshalled class

NOTES

  • Of course this would not work for complex (nested) data structures.
  • You have to think how to unmarshall this back again, could be more tricky. If it's too tricky, wait for a better proposal than mine ;)

Upvotes: 5

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

So it looks like the problem is the erasure of E on start and end is Comparable. If it can't handle interfaces you could try Object, but I would hope it complains at that too (either now or later). Possibly you could make Range abstract and specialise it for each specific E. I should know more about JAXB.

Upvotes: -1

ng.
ng.

Reputation: 7189

Try something like Simple XML Serialization it comes with support for generic types in XML elements with a number of annotations such as @Element and @ElementList. The programming model is very similar, but simpler than JAXB.

Upvotes: 0

Related Questions