Search code examples
xsdjaxbjaxb2-maven-pluginjaxb2-basicsjaxb-xew-plugin

Java XSD - using a custom collection type


My Issue

I am using the jaxb2 maven plugin to convert my XSD defined objects into Java classes. My goal is to set a list type element in my XSD (such as xs:choice unbound), to LinkedList instead of using the default ArrayList type. I am using the jaxb-xew-plugin version 1.10. Here is my relevant code:

XSD:

<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema jaxb:version="2.0"
           xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
           xmlns:xew="http://github.com/jaxb-xew-plugin"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           jaxb:extensionBindingPrefixes="xew"
           elementFormDefault="qualified">

    <xs:element name="TEST">
        <xs:complexType>
            <xs:annotation>
                <xs:appinfo>
                    <xew:xew collection="java.util.LinkedList"
                             collectionInterface="java.util.List"
                             instantiate="lazy"
                             plural="true"/>
                </xs:appinfo>
            </xs:annotation>
            <xs:choice>
                <xs:element name="action" type="xs:token" minOccurs="0" maxOccurs="unbounded">
                </xs:element>
            </xs:choice>
        </xs:complexType>
    </xs:element>

</xs:schema>

POM:

    <build>
        <resources>
            <resource>
                <directory>src/main/xsd</directory>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>jaxb2-maven-plugin</artifactId>
                <version>2.5.0</version>
                <executions>
                    <execution>
                        <id>xjc</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>xjc</goal>
                        </goals>
                        <configuration>
                            <sources>src/main/xsd</sources>
                            <packageName>com.tug.data.model.gen</packageName>
                            <verbose>true</verbose>
                            <clearOutputDir>false</clearOutputDir>
                            <extension>true</extension>
                            <arguments>
                                <argument>-Xsetters</argument>
                                <argument>-Xxew</argument>
                                <argument>-Xfluent-api</argument>
                                <argument>-Xjaxbindex</argument>
                                <argument>-Xequals</argument>
                                <argument>-XhashCode</argument>
                                <argument>-XtoString</argument>
                                <argument>-Xcopyable</argument>
                                <argument>-Xmergeable</argument>
                            </arguments>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>com.github.jaxb-xew-plugin</groupId>
                        <artifactId>jaxb-xew-plugin</artifactId>
                        <version>1.10</version>
                    </dependency>
                    <dependency>
                        <groupId>net.java.dev.jaxb2-commons</groupId>
                        <artifactId>jaxb-fluent-api</artifactId>
                        <version>2.1.8</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jvnet.jaxb2_commons</groupId>
                        <artifactId>jaxb2-basics</artifactId>
                        <version>0.12.0</version>
                    </dependency>
                </dependencies>
            </plugin>

Resulting Java code generated

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "action"
})
@XmlRootElement(name = "TEST")
public class TEST implements Cloneable, CopyTo2, Equals2, HashCode2, MergeFrom2, ToString2
{

    @XmlElement(required = true)
    protected List<Action> action;

    public List<Action> getAction() {
        if (action == null) {
            action = new ArrayList<Action>(); // <--- **This should have been LinkedList**
        }
        return this.action;
    }

As you can see, the ArrayList type is still coming out instead of LinkedList. It actually seems like the xew arguments and commands are being ignored entirely... I don't get any errors

I have tried many variations of this, copying and pasting the xs:annotation blurb pretty much in every combination of locations I could logically think of. The only error I get is here:

    <xs:element name="TEST">
        <xs:complexType>
            <xs:choice>
                <xs:element name="action" type="xs:token" minOccurs="0" maxOccurs="unbounded">
                    <xs:annotation>
                        <xs:appinfo>
                            <xew:xew collection="java.util.LinkedList"
                                     collectionInterface="java.util.List"
                                     instantiate="lazy"
                                     plural="true"/>
                        </xs:appinfo>
                    </xs:annotation>
                </xs:element>
            </xs:choice>
        </xs:complexType>
    </xs:element>

This combo results in: com.sun.istack.SAXParseException2: compiler was unable to honor this xew customization. It is attached to a wrong place, or its inconsistent with other bindings.

Do you see any missing step which would result in my custom collection override would not be picked up on?

I attached the maven debug output, it is a lot to look through and I don't pick up any hints there. mvn-debug.log

Or...

Is there another way to use a custom List type when generating my Java objects from XSD?

(PS. I cross-posted this on the github section for the jaxb-xew-plugin, however I realized the last commit was over 2 years ago so it may be a dormant project. Posting here for help in the SOF community)


Solution

  • In the scope of the XSD that you have provided the customization does make sense. You need to understand the role of Xew plugin: if type A has some filed which is complex like type TEST in our example, then we have a "matroska" A→TEST→List, and in this case plugin tries to remove type TEST from the model. Now you have provided only TEST in your example, this is only half of the story, the plugin does not do anything in this case. You need also to provide another type A that uses TEST, for example:

    <xs:element name="A">
        <xs:complexType>
            <xs:sequence>
                <xs:element ref="TEST">
                    <xs:annotation>
                        <xs:appinfo>
                            <xew:xew collection="java.util.LinkedList"
                                     collectionInterface="java.util.List"
                                     instantiate="lazy"
                                     plural="true"/>
                        </xs:appinfo>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    
    <xs:element name="TEST">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="action" type="xs:token" minOccurs="0" maxOccurs="unbounded" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
    

    And that is where plugin is powered on. You will see in the output:

    [INFO] Modifications:
    [INFO]     Replacing field [TEST com.tug.data.model.gen.A#test]
    [INFO]     1 modification(s) to original code.
    [INFO]
    [INFO] Deletions:
    [INFO]     Removing class com.tug.data.model.gen.TEST from package com.tug.data.model.gen
    [INFO]     Removing factory method [com.tug.data.model.gen.TEST#createTEST()] from com.tug.data.model.gen.ObjectFactory
    [INFO]     2 deletion(s) from original code.
    

    and the resulting class will be (I removed unrelated staff):

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "", propOrder = {
        "tests"
    })
    @XmlRootElement(name = "A")
    public class A implements Cloneable, CopyTo2, Equals2, HashCode2, MergeFrom2, ToString2
    {
    
        @XmlElementWrapper(name = "TEST", required = true)
        @XmlElement(name = "action")
        @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
        protected List<String> tests;
    
        public List<String> getTESTS() {
            if (tests == null) {
                tests = new LinkedList<String>();
            }
            return tests;
        }
    
        public void setTESTS(List<String> tests) {
            this.tests = tests;
        }
    
        public A withTESTS(String... values) {
            if (values!= null) {
                for (String value: values) {
                    getTESTS().add(value);
                }
            }
            return this;
        }
    
        public A withTESTS(Collection<String> values) {
            if (values!= null) {
                getTESTS().addAll(values);
            }
            return this;
        }
    
        public A withTESTS(List<String> tests) {
            setTESTS(tests);
            return this;
        }
    
        ...
    }
    

    and that is where LinkedList pops up.

    So plugin does not modify TEST type. Basically, the policy would be "remove or leave it alone". If you want to customize type TEST on its own, you need to use native JAXB customization collectionType:

    <?xml version="1.0" encoding="UTF-8" ?>
    <xs:schema jaxb:version="2.0"
               xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
               xmlns:xs="http://www.w3.org/2001/XMLSchema"
               elementFormDefault="qualified">
    
        <xs:element name="TEST">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="action" type="xs:token" minOccurs="0" maxOccurs="unbounded">
                        <xs:annotation>
                            <xs:appinfo>
                                <jaxb:property collectionType="java.util.LinkedList" />
                            </xs:appinfo>
                        </xs:annotation>
                    </xs:element>
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xs:schema>