In a REST server that I've written, I have several collection classes that wrap single items to be returned from my services:
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "person_collection")
public final class PersonCollection {
@XmlElement(name = "person")
protected final List<Person> collection = new ArrayList<Person>();
public List<Person> getCollection() {
return collection;
}
}
I would like to refactor these to use generics so the the boilerplate code can be implemented in a superclass:
public abstract class AbstractCollection<T> {
protected final List<T> collection = new ArrayList<T>();
public List<T> getCollection() {
return collection;
}
}
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name = "person_collection")
public final class PersonCollection extends AbstractCollection<Person> {}
How do I set the @XmlElement
annotation on the superclass collection? I am thinking of something involving a @XmlJavaTypeAdapter
and reflection, but was hoping for something simpler. How do I create the JAXBContext
? BTW, I am using RestEasy 1.2.1 GA for the JAX-RS front-end.
UPDATE (for Andrew White): Here is code that demonstrates getting the Class
object for the type parameter(s):
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
public class TestReflection
extends AbstractCollection<String> {
public static void main(final String[] args) {
final TestReflection testReflection = new TestReflection();
final Class<?> cls = testReflection.getClass();
final Type[] types = ((ParameterizedType) cls.getGenericSuperclass()).getActualTypeArguments();
for (final Type t : types) {
final Class<?> typeVariable = (Class<?>) t;
System.out.println(typeVariable.getCanonicalName());
}
}
}
class AbstractCollection<T> {
protected List<T> collection = new ArrayList<T>();
}
Here is the output: java.lang.String
.
Reflection Issue
The following is the reflection test that needs to be made work. I believe type erasure is what is preventing this from happening:
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TestReflection extends AbstractCollection<String> {
private List<Integer> childCollection = new ArrayList<Integer>();
public List<Integer> getChildCollection() {
return childCollection;
}
public static void main(final String[] args) throws Exception {
final TestReflection testReflection = new TestReflection();
final Class<?> cls = testReflection.getClass();
Method method1 = cls.getMethod("getChildCollection", new Class[] {});
System.out.println(method1.getGenericReturnType());
Method method2 = cls.getMethod("getCollection", new Class[] {});
System.out.println(method2.getGenericReturnType());
}
}
The above code will output what is shown below. This is because the "getCollection" method is in the context of AbstractCollection and not TestReflection. This is to ensure backwards compatibility of the Java binaries:
java.util.List<java.lang.Integer>
java.util.List<T>
Alternative Approach
If the items in the collection will be annotated with @XmlRootElement, then you could achieve what you want to do with the following:
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAnyElement;
public abstract class AbstractCollection<T> {
protected List<T> collection = new ArrayList<T>();
@XmlAnyElement(lax=true)
public List<T> getCollection() {
return collection;
}
}
And assuming Person looks like the following:
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Person {
}
Then the following demo code:
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
public class Demo {
public static void main(String[] args) throws Exception {
JAXBContext jc = JAXBContext.newInstance(PersonCollection.class, Person.class);
PersonCollection pc = new PersonCollection();
pc.getCollection().add(new Person());
pc.getCollection().add(new Person());
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(pc, System.out);
}
}
Will produce:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<person_collection>
<person/>
<person/>
</person_collection>
For more information see:
- http://bdoughan.blogspot.com/2010/08/using-xmlanyelement-to-build-generic.html
I think what you are asking is not possible due to type erasure. Any generic solution would lose the "type" of the container needed for unmarshalling.