Skip to main content

@XmlElementRef supported on an variable whose type is an interface?

16 replies [Last post]
grunge
Offline
Joined: 2005-10-12

I can sucessfully annotate a variable with @XmlElementRef when the variable type is a real subclass. However it seems that I cannot annotate a variable with @XmlElementRef when the variable type is an interface. Am I missing something?

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kohsuke
Offline
Joined: 2003-06-09

> 1) If I can get it to work with an abstract class, does
> that mean that I should also be able to get it to work
> with an interface, or does this only work with abstract
> classes?

I believe it works with interfaces, too.

As for the NPE, it does look similar to what's reported as issue 112. But I'm sure that particular one is fixed.

It would be great if you can file a new issue, perhaps by pointing out a relevant to 112, and with a test case that we can look into.

Thanks!

vk101
Offline
Joined: 2007-11-18

From the code below, assume you refer to InterfaceType from multiple different classes. It seems redundant to specify the following on every property that refers to InterfaceType:

[code]
@XmlElementRefs({
@XmlElementRef(type=A.class),
@XmlElementRef(type=B.class)
})
[/code]

After all, if A and B are always implementations of InterfaceType, then shouldn't you be able to specify the @XmlElementRefs on InterfaceType itself rather than on every property that refers to InterfaceType?

I'm dreading the situation where InterfaceType is referred to in hundreds of places, and you want to add support for a new implementation of InterfaceType (i.e. C.class).

I'd imagine there's a way to do this - and if not, is this a sensible feature request? (Do others who use JAXB just live with this redundancy, or is there some other JAXB feature I've yet to learn that lets us get around this?)

> [code]
> @XmlElementRefs({
> @XmlElementRef(type=A.class),
> @XmlElementRef(type=B.class)
> })
> private InterfaceType getInterfaceType()
> {
> return interfaceType;
>
>
> @XmlRootElement(name="a")
> public class A implements InterfaceType
> {
> }
>
> @XmlRootElement(name="b")
> public class B implements InterfaceType
> {
> }
> [/code]

tigera
Offline
Joined: 2007-07-15

I don't think that you can put this annotation at package level, though you are right, it'd be nice if you could. XmlElementRef is most useful when you can depend on it to infer the type of the object for you.
Another way to pull this off would be to annotate the interface in question with XmlJavaTypeAdapter. This way, you only have to do it once, with the downside of course being that you would have to write the adapter in question. Or you could just use the XmlAnyType annotation instead.

vk101
Offline
Joined: 2007-11-18

Thanks, I will give these suggestions a try and see how they work.

rationalpi
Offline
Joined: 2005-10-14

kohsuke-

I tried one of your suggestions above with a typesafe list of abstract clases. It seems to work partly, but I have two questions:

1) If I can get it to work with an abstract class, does that mean that I should also be able to get it to work with an interface, or does this only work with abstract classes?

2) I'm trying to use @XmlElementWrapper with the typesafe list and the @XmlElementRefs so that I have all of the elements of the list wrapped in tags. It marshalls correctly, but throws a null pointer exception when I try to unmarshal.

Here are some snippets to show you what I'm trying to do.

I'm looking for XML that is something like this:












My classes (at least the relevant ones) look like this:
public class Job

@XmlTransient
private List actions;

@XmlElementWrapper(name="actions")
@XmlElementRefs({
@XmlElementRef(name="split", type=Split.class),
@XmlElementRef(name="zip", type=Zip.class)
)}
public void setActions(List actions) {
this.actions = actions;
}

---
@XmlRootElement(name="split")
public class Split extends AbstractAction {

---
@XmlRootElement(name="zip")
public class Zip extends AbstractAction {

---
public abstract class AbstractAction

---

I have a JobList class as well that holds a list of Job objects. It's the root level of my document and is represented as a element. It's working fine so I didn't list it here.

The exception:
Exception in thread "main" java.lang.NullPointerException at fmod.job.Job$JaxbAccessorM_getActions_setActions_java_util_List.get(MethodAccessor_Ref.java:16) at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Lister.java:249) at com.sun.xml.bind.v2.runtime.reflect.Lister$CollectionLister.startPacking(Lister.java:224) at com.sun.xml.bind.v2.runtime.unmarshaller.Scope.add(Scope.java:78) at com.sun.xml.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(ArrayERProperty.java:150) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(UnmarshallingContext.java:404) at com.sun.xml.bind.v2.runtime.unmarshaller.ValidatingUnmarshaller.endElement(ValidatingUnmarshaller.java:73) at com.sun.xml.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:125) at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(Unknown Source) at com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(Unknown Source) at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(Unknown Source) at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) at com.sun.org.apache.xeres.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at com.sun.org.apache.xerces.internal.parsers.XML11Cofiguration.parse(Unknown Source) at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown source) at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source) at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(Unknown Source) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:200) at com.sun.xml.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:173) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:137) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:142) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:151) at javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarhsal(AbstractUnmarshallerImpl.java:169) at Main.main(Main.java:64)

So what do you think?

Thanks,
Joshua Smith

rationalpi
Offline
Joined: 2005-10-14

After a little more digging it looks like this might be similar to an exception discussed here:
http://forums.java.net/jive/thread.jspa?threadID=2424&messageID=34419

This is the associated bug:
https://jaxb.dev.java.net/issues/show_bug.cgi?id=112

It's unclear from the text of the bug report whether this issue was fixed, or was fixed, broken and reopened.

I'm using the version of JAXB 2.0 that is distributed with the Java Web Services Developement Pack (JWSDP) if that helps.

Joshua Smith

grunge
Offline
Joined: 2005-10-12

It turns our that I can use @XmlElements(@XmlElement(), ...)) instead of @XmlElementRef to accomplish the same thing and it works on variables whose type is an interface. Another nice side-effect is that I don't have to tell JAXBContext about the reference types since they are already specified in the individual @XmlElement() above.

Therefore, what is the purpose of @XmlElementRef?

kohsuke
Offline
Joined: 2003-06-09

The purpose of @XmlElementRef is so that you can specify the tag name on classes, not on the field.

This makes it easy to have extensible content models. @XmlElement requires that you list up all the element names and type mapping in the property.

grunge
Offline
Joined: 2005-10-12

OK, that makes sense. Thanks for the information.

grunge
Offline
Joined: 2005-10-12

One more thing - In my case I would switch back to @XmlElementRef because it's somewhat cleaner and more extensible, however it does not work when the property being annotated is an interface type. In this case it produces as error like the following:

IllegalAnnotationExceptions does not have a no-arg default constructor.

kohsuke
Offline
Joined: 2003-06-09

That is true regardless of whether it's @XmlElement or @XmlElementRef.

Use XmlElement.type() or XmlElementRef.type() to specify the class (like FooImpl, not Foo) to avoid referring JAXB to interfaces.

grunge
Offline
Joined: 2005-10-12

In EA2 and EA3, @XmlElements can be specified on a property with an interface type and it works great. It does not work with @XmlElementRef.

I realize I can use a real class instead (ie: FooImpl), but there are times when a POJO needs a field/property with an interface type. Should not JAXB support this?

kohsuke
Offline
Joined: 2003-06-09

I don't think @XmlElement.type() is allowed to be an interface.

Any test case?

grunge
Offline
Joined: 2005-10-12

Yeah, @XmlElement.type can't be an interface, but I'm talking about @XmlElements (with the 's'):

This @XmlElements() structure works on interface types:

-----

@XmlElements
({
@XmlElement(name="a", type=A.class),
@XmlElement(name="b", type=B.class)
})
private InterfaceType getInterfaceType()
{
return interfaceType;
}

public class A implements InterfaceType
{
}

public class B implements InterfaceType
{
}

-----

This @XmlElementRef structure does not work on interface types:

@XmlElementRef
private InterfaceType getInterfaceType()
{
return interfaceType;
}

@XmlRootElement(name="a")
public class A implements InterfaceType
{
}

@XmlRootElement(name="b")
public class B implements InterfaceType
{
}

-----

It yields an error like this:

IllegalAnnotationExceptions does not have a no-arg default constructor.

It would be nice if the @XmlElementRef way worked as well since then one could take advantage of its extensibility, however I may not be aware of the difficulties/ramifications of making this work.

kohsuke
Offline
Joined: 2003-06-09

You can do:

[code]
@XmlElementRefs({
@XmlElementRef(type=A.class),
@XmlElementRef(type=B.class)
})
private InterfaceType getInterfaceType()
{
return interfaceType;
}

@XmlRootElement(name="a")
public class A implements InterfaceType
{
}

@XmlRootElement(name="b")
public class B implements InterfaceType
{
}
[/code]

Or if your implementation classes derive from the same (potentially abstract) class, then you can do like this:

[code]
@XmlElementRef(type=InterfaceImpl.class)
private InterfaceType getInterfaceType()
{
return interfaceType;
}

public abstract class InterfaceImpl {}

@XmlRootElement(name="a")
public class A extends InterfaceImpl
{
}

@XmlRootElement(name="b")
public class B implements InterfaceImpl
{
}
[/code]

grunge
Offline
Joined: 2005-10-12

Ok, thanks! Neither of those ways occurred to me, but they are both very "clean" for my particular problem, especially the first one. I think I have a better understanding of things now.