Reputation: 925
Is there any JAXB binding that can tell the JAXB code generator to generate a Java class as abstract
without having to mark the corresponding XML type as abstract
in the XSD?
The situation is as follows:
mySchema.xsd
I use inline JAXB bindings ("inline" == "directly in the schema") to indicate the package where the JAXB classes should be generated (my.package.jaxb
):
<xs:annotation>
<xs:appinfo>
<jxb:schemaBindings>
<jxb:package name="my.package.jaxb"/>
</jxb:schemaBindings>
</xs:appinfo>
</xs:annotation>
I use inline JAXB bindings to indicate the name of the implementation class for each of my complex types (in this example my.package.impl.MyAbstractClass
, my.package.impl.MyAClass
and my.package.impl.MyBClass
):
<xs:complexType name="myAbstractType" abstract="true">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyAbstractClass"/>
</xs:appinfo>
</xs:annotation>
...
</xs:complexType>
<xs:complexType name="myAType">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyAClass"/>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="myAbstractType">
...
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="myBType">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyBClass"/>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="myAbstractType">
...
</xs:extension>
</xs:complexContent>
</xs:complexType>
I generate the JAXB class from the schema. This results in:
my.package.jaxb
|- MyAbstractType
|- MyAType (extends MyAbstractClass)
|- MyBType (extends MyAbstractClass)
I write my own classes:
my.package.impl
|- MyAbstractClass (extends MyAbstractType)
|- MyAClass (extends MyAType)
|- MyBClass (extends MyBType)
The reason I do it like this, with these 2 class hierarchies, is so that I can separate the generated code (my.package.jaxb.*
) from the manual (my.package.impl.*
). This way when there are changes in the XSD I can regenerate the my.package.jaxb.*
classes and make a few changes in my manual my.package.impl.*
classes to incorporate the new behaviour.
So far so good. The issue is that in MyAbstractClass
I want to define an abstract method...
protected abstract void doSomething();
...that is then implemented differently by MyAClass
and MyBClass
.
However, the generated MyAType
and MyBType
classes now have compilation errors because they are not declared as abstract but they now inherit an abstract method (notice that they both extend MyAbstractClass
).
I cannot declare them as abstract in the XSD (abstract="true"
) because doing so would result in the following error whenever I declare an element of type myAType
or myBType
in an XML:
cvc-type.2: The type definition cannot be abstract for element someElementName.
What I would like is to use some JAXB binding to tell the JAXB code generator to generate the classes MyAType
and MyBType
as abstract
without having to mark the XML types as abstract
. Is there such a binding? I haven't been able to find it so far.
Sorry for the long explanation, and thanks in advance.
Upvotes: 5
Views: 1485
Reputation: 925
I ended up creating a XJC plugin. Here's the code:
import java.lang.reflect.Method;
import javax.xml.namespace.QName;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JMods;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BIDeclaration;
import com.sun.tools.xjc.reader.xmlschema.bindinfo.BindInfo;
import com.sun.xml.xsom.XSAnnotation;
public class AbstractModifierPlugin extends com.sun.tools.xjc.Plugin {
private static final QName ABSTRACT_QNAME = new QName("http://www.example.com/jaxb/abstract-modifier/1-0", "abstract");
private static final String SET_FLAG_METHOD_NAME = "setFlag";
private static final String OPTION_NAME = "Xabstract-modifier";
@Override
public String getOptionName() {
return OPTION_NAME;
}
@Override
public String getUsage() {
return " -" + OPTION_NAME + " : marks as abstract the generated classes corresponding to XML types marked with "
+ "<xs:annotation><xs:appinfo><" + ABSTRACT_QNAME + "/></xs:appinfo></xs:annotation>";
}
@Override
public boolean run(Outline outline, Options options, ErrorHandler errorHandler) throws SAXException {
Method setFlagMethod = null;
try {
// There is no method to make a class abstract; we can only use setFlag, which is private, so
// we must get it via reflection and make it accessible.
setFlagMethod = JMods.class.getDeclaredMethod(SET_FLAG_METHOD_NAME, int.class, boolean.class);
setFlagMethod.setAccessible(true);
} catch (Throwable e) {
System.err.println("There was an error retrieving the " + JMods.class.getName() + "." + SET_FLAG_METHOD_NAME
+ " method (see below) => it will not be possible to set any class' abstract flag => this plugin will abort");
e.printStackTrace();
return false;
}
for (ClassOutline classOutline : outline.getClasses()) {
if (hasAbstractAnnotation(classOutline)) {
try {
setFlagMethod.invoke(classOutline.implClass.mods(), JMod.ABSTRACT, true);
} catch (Throwable e) {
System.err.println("It was not possible to make " + classOutline.implClass.fullName()
+ " abstract (see below)");
e.printStackTrace();
}
}
}
return true;
}
protected boolean hasAbstractAnnotation(ClassOutline classOutline) {
XSAnnotation annotation = classOutline.target.getSchemaComponent().getAnnotation();
if (annotation != null) {
Object innerAnnotation = annotation.getAnnotation();
if (innerAnnotation instanceof BindInfo) {
for (BIDeclaration bindInfoDeclaration : (BindInfo) innerAnnotation) {
if (ABSTRACT_QNAME.equals(bindInfoDeclaration.getName())) {
return true;
}
}
}
}
return false;
}
}
Here's abstract.xsd
, which defines the <abstract>
element you need to use to indicate that the generated class should be abstract:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.example.com/jaxb/abstract-modifier/1-0"
xmlns:tns="http://www.example.com/jaxb/abstract-modifier/1-0"
elementFormDefault="qualified">
<element name="abstract"/>
</schema>
Usage (following the example in my original question):
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
...
xmlns:abstract="http://www.example.com/jaxb/abstract-modifier/1-0"
...>
<xs:complexType name="myAbstractType" abstract="true">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyAbstractClass"/>
</xs:appinfo>
</xs:annotation>
...
</xs:complexType>
<xs:complexType name="myAType">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyAClass"/>
<!-- This tells the AbstractModifierPlugin to make the
generated class abstract -->
<abstract:abstract/>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="myAbstractType">
...
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="myBType">
<xs:annotation>
<xs:appinfo>
<jxb:class implClass="my.package.impl.MyBClass"/>
<!-- This tells the AbstractModifierPlugin to make the
generated class abstract -->
<abstract:abstract/>
</xs:appinfo>
</xs:annotation>
<xs:complexContent>
<xs:extension base="myAbstractType">
...
</xs:extension>
</xs:complexContent>
</xs:complexType>
I have to say I found it pretty hard to find all the necessary documentation to figure out how to do this. If I find the time I will post a longer explanation here; for now I hope at least the code will help. Disclaimer: I don't know whether this is the best way to do this but the classes I had to rely on are so loosely documented (and IMHO not very well designed) that that's the best I could come up with.
I hope this helps. If anyone wants to use this code, go ahead.
Upvotes: 0