JAXB binding to generate abstract Java class from non-abstract XML type

Shorter

Is there any JAXB binding that can tell the JAXB code generator to generate the Java class as abstract

without having to mark the corresponding XML type as abstract

in XSD?

Description

The situation is as follows:

  • I am defining a schema in XSD: mySchema.xsd

  • I use the inline JAXB bindings ("inline" == "directly in the schema") to specify the package in which the JAXB ( my.package.jaxb

    ) classes should be generated :

    <xs:annotation>
        <xs:appinfo>
            <jxb:schemaBindings>
                <jxb:package name="my.package.jaxb"/>
            </jxb:schemaBindings>
        </xs:appinfo>
    </xs:annotation>
    
          

  • I use the built-in JAXB bindings to specify 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 am generating JAXB class from schema. This leads to:

    my.package.jaxb
       |- MyAbstractType
       |- MyAType (extends MyAbstractClass)
       |- MyBType (extends MyAbstractClass)
    
          

  • I am writing my own classes:

    my.package.impl
       |- MyAbstractClass (extends MyAbstractType)
       |- MyAClass (extends MyAType)
       |- MyBClass (extends MyBType)
    
          

The reason I am doing this, with these two class hierarchies, is because 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 classes my.package.jaxb.*

and make a few changes to my manual classes my.package.impl.*

to enable the new behavior.

So far so good. The problem is that in MyAbstractClass

I want to define an abstract method ...

protected abstract void doSomething();

      

... which is then executed differently with MyAClass

and MyBClass

. However, the generated classes MyAType

and MyBType

now have compilation errors because they are declared as abstract, but they inherit from the abstract method (note that both are expanding MyAbstractClass

).

I cannot declare them abstract in XSD ( abstract="true"

) because it will throw the following error whenever I declare an element of type MyAType

or MyBType

in XML:

cvc-type.2: The type definition cannot be abstract for element someElementName.

      

I would like to use some JAXB binding to tell the JAXB code generator to generate classes MyAType

and MyBType

how abstract

to unnecessarily mark the XML types as abstract

. Is there such a binding? I still haven't been able to find it.

Sorry for the long explanation and thanks in advance.

+3


source to share


1 answer


I ended up creating an 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 abstract.xsd

's which defines the element <abstract>

to be used 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 (after 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 that it was quite difficult for me to find all the necessary documentation to figure out how to do this. If I can find the time, I will post a longer explanation here; for now i hope that at least the code helps. Disclaimer: I don't know if this is the best way to do it, but the classes I had to rely on are so poorly documented (and IMHO not well designed) that they are the best I could think of.

Hope this helps. If anyone wants to use this code, go ahead.

0


source







All Articles