I'd like to unmarshall JAXB beans into subclasses, based on attribute value.
MOXy can do that:
@XmlDiscriminatorNode("@type")
public static class ActionDef extends ContainerOfStackableDefs { ... }
@XmlDiscriminatorValue("cli")
public static class CliActionDef extends ActionDef {
/** CLI command. EL. */
@XmlAttribute public String command;
}
However, the JAXBContext needs all the classes specified. Which is kind of unpleasant to define somewhere else than in the "root" class.
I was hoping for something like:
@XmlDiscriminatorNode("@type")
@XmlSubClasses({ CliActionDef.class, XsltActionDef.class, ... })
public static class ActionDef extends ContainerOfStackableDefs { ... }
I don't want to get into XmlAdapters, this is a trivial task and should have trivial solution.
Is there something, either spec or MOXy extension, I could use to simply list the subclasses in the root class?
Otherwise, I'm about to use Jandex to find the subclasses automatically. Afterall, MOXy could do that too ;-)
Update: For the record, then I was getting
Descriptor: XMLDescriptor(org.jboss.loom.migrators._ext.MigratorDefinition$ActionDef --> [])
at org.eclipse.persistence.exceptions.DescriptorException.missingClassIndicatorField(DescriptorException.java:957)
...
Which is described here: Exception : "Missing class indicator field from database row [UnmarshalRecordImpl()]." when unmarshalling XML using EclipseLink JAXB (MOXy)
In short: Don't have MOXy unmarshalling to the same-named property as is the name in @XmlDiscriminatorNode("@name"). If you do, then have it @XmlReadOnly and don't have the class abstract.
Update: I still can't get it work. I keep getting the base class. Processed by MOXy, not JDK's impl.
My code:
<action type="xslt" var="addAction" ...>
</action>
<action type="manual">
...
</action>
Beans:
//@XmlRootElement
@XmlDiscriminatorNode("@type")
@XmlSeeAlso({ CliActionDef.class, ModuleActionDef.class, CopyActionDef.class, XsltActionDef.class })
public static class ActionDef extends ContainerOfStackableDefs {
//@XmlAttribute
//@XmlReadOnly
public String typeVal; // Was type, caused an exception.
//public List<PropertyBean> properties;
//@XmlAnyAttribute
public Map<String, String> attribs = new HashMap();
}
@XmlRootElement
@XmlDiscriminatorValue("manual")
public static class ManualActionDef extends ActionDef {
}
public static class FileBasedActionDef extends ActionDef {
/** Path mask. Ant-like wildcards, EL. */
@XmlAttribute public String pathMask;
/** Where to store the result. May be a dir or a file. EL. */
@XmlAttribute public String dest;
}
@XmlRootElement
@XmlDiscriminatorValue("xslt")
public static class XsltActionDef extends FileBasedActionDef {
/** XSLT template path. */
@XmlAttribute public String xslt;
}
What's wrong?
Update:
After putting @XmlDiscriminatorNode("@type") to the top-most class ContainerOfStackableDefs, I get:
Exception Description: Missing class for indicator field value [manual] of type [class java.lang.String].
Descriptor: XMLDescriptor(org.jboss.loom.migrators._ext.MigratorDefinition$ActionDef --> [])
at org.eclipse.persistence.exceptions.DescriptorException.missingClassForIndicatorFieldValue(DescriptorException.java:938)
at org.eclipse.persistence.internal.oxm.QNameInheritancePolicy.classFromRow(QNameInheritancePolicy.java:264)
In short: Missing java.lang.String. :-o Solvable?
Also, I created a simplest test case:
public class JaxbInheritanceTest {
@Test
public void testUnmarshall() throws JAXBException{
final Unmarshaller marshaller = XmlUtils.createJaxbContext(Root.class).createUnmarshaller();
Root root = (Root) marshaller.unmarshal( new StringReader(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<root><sub disc=\"foo\"/><sub disc=\"bar\"/></root>") );
boolean rightClass = (DiscFoo.class.isAssignableFrom( root.subs.get(0).getClass() ));
Assert.assertTrue( "base elements go into subclasses", rightClass );
rightClass = (DiscBar.class.isAssignableFrom( root.subs.get(1).getClass() ));
Assert.assertTrue( "base elements go into subclasses", rightClass );
}
@XmlRootElement
public static class Root {
@XmlElement(name = "sub")
List<Base> subs;
}
@XmlDiscriminatorNode("@disc")
@XmlSeeAlso({DiscFoo.class, DiscBar.class})
public static abstract class Base {}
@XmlRootElement @XmlDiscriminatorValue("foo")
public static class DiscFoo {}
@XmlRootElement @XmlDiscriminatorValue("bar")
public static class DiscBar {}
}
But this also gives me 2x Base. It's handled by MOXy - checked in debugger.
What's wrong here?