javajaxbunmarshalling

JAXB unmarshalling unit test succeeds in Eclipse but fails with "JAXB can't handle interfaces" with gradle test


This is a unit test that works sucessfully when I run it in Eclipse but fails in when I run it with gradle command line 'gradle test'.

package com.mycompany.user;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class ImportUserTest {

    @Test
    public void testUnmarshall() throws Exception {
        ImportUser importUser = new ImportUser();
        UserList userList = importUser.unmarshall("src/test/resources/userList.xml");
        assertEquals("John Doe", userList.getUser().get(0).getFullName());
    }
}

ImportUser class is like below:

package com.mycompany.user;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

public class ImportUser {

    private static final String PACKAGE_CONTEXT = "com.mycompany.user";

    public UserList unmarshall(String inputFile) throws JAXBException, FileNotFoundException, ParserConfigurationException, SAXException {
        JAXBContext jc = JAXBContext.newInstance(PACKAGE_CONTEXT);
        Unmarshaller u = jc.createUnmarshaller();
        return (UserList) JAXBUtilities.safeUnmarshall(u, new FileInputStream(inputFile));
    }
}

This JAXBUtilities class is as follows:

package com.mycompany.user;

import java.io.InputStream;
import java.io.Reader;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class JAXBUtilities {

    private JAXBUtilities() {
    }

    public static Object safeUnmarshall(Unmarshaller u, InputStream in) throws ParserConfigurationException, SAXException, JAXBException {
        return safeUnmarshall(u, new InputSource(in));
    }

    public static Object safeUnmarshall(Unmarshaller u, Reader reader) throws ParserConfigurationException, SAXException, JAXBException {
        return safeUnmarshall(u, new InputSource(reader));
    }
    
    public static Object safeUnmarshall(Unmarshaller u, InputSource inputSource) throws ParserConfigurationException, SAXException, JAXBException {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setValidating(true);
        spf.setNamespaceAware(true);
        spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        spf.setFeature("http://xml.org/sax/features/validation", false);
        spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        spf.setXIncludeAware(false);

        Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), inputSource);
        return u.unmarshal(xmlSource);
    }
}

Below the XML input:

<userList>
  <user name="string">
    <fullName>John Doe</fullName>
    <description>BO user</description>
    <lastPropertyNum>3</lastPropertyNum>
    <loginIddleDays>3</loginIddleDays>
    <maxLoginAttempt>3</maxLoginAttempt>
    <maxTrades>3</maxTrades>
    <passwordCheckDigit>true</passwordCheckDigit>
    <passwordCheckSpecialChar>false</passwordCheckSpecialChar>
    <passwordMinLength>3</passwordMinLength>
    <passwordChangeDue>false</passwordChangeDue>
    <thresholdDays>3</thresholdDays>
    <updateUser>string</updateUser>
    <version>3</version>
    <processingOrg>ALL</processingOrg>
  </user>
</userList>

Below the XSD:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    
    <xsd:complexType name="userType">
        <xsd:sequence>
            <xsd:element name="fullName" minOccurs="0" type="xsd:string"/>
            <xsd:element name="description" minOccurs="0" type="xsd:string"/>
            <xsd:element name="lastPropertyNum" minOccurs="0" type="xsd:int"/>
            <xsd:element name="loginIddleDays" minOccurs="0" type="xsd:int"/>
            <xsd:element name="maxLoginAttempt" minOccurs="0" type="xsd:int"/>
            <xsd:element name="maxTrades" minOccurs="0" type="xsd:int"/>
            <xsd:element name="passwordCheckDigit" minOccurs="0" type="xsd:boolean"/>
            <xsd:element name="passwordCheckSpecialChar" minOccurs="0" type="xsd:boolean"/>
            <xsd:element name="passwordMinLength" minOccurs="0" type="xsd:int"/>
            <xsd:element name="passwordChangeDue" minOccurs="0" type="xsd:boolean"/>
            <xsd:element name="thresholdDays" minOccurs="0" type="xsd:int"/>
            <xsd:element name="updateUser" minOccurs="0" type="xsd:string"/>
            <xsd:element name="version" minOccurs="0" type="xsd:int"/>
            <xsd:element name="processingOrg" minOccurs="0" maxOccurs="unbounded" type="xsd:string"/>
        </xsd:sequence>
        <xsd:attribute name="name" type="xsd:string"/>
    </xsd:complexType>
    
    <xsd:element name="userList">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="user" minOccurs="1" maxOccurs="unbounded" type="userType"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    
</xsd:schema>

I removed the comments from the generated classes below (generated by JAXB 2.1.3) :

package com.mycompany.user;

import javax.xml.bind.annotation.XmlRegistry;

@XmlRegistry
public class ObjectFactory {

    public UserType createUserType() {
        return new UserTypeImpl();
    }

    public UserList createUserList() {
        return new UserListImpl();
    }
}
package com.mycompany.user;

import java.util.List;

public interface UserList {

    List<UserType> getUser();

}
package com.mycompany.user;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = { "user" })
@XmlRootElement(name = "userList")
public class UserListImpl implements UserList {

    @XmlElement(required = true, type = UserTypeImpl.class)
    protected List<UserType> user;

    @Override
    public List<UserType> getUser() {
        if (user == null) {
            user = new ArrayList<>();
        }
        return user;
    }

}
package com.mycompany.user;

import java.util.List;

public interface UserType {
    String getFullName();
    void setFullName(String value);
    String getDescription();
    void setDescription(String value);
    Integer getLastPropertyNum();
    void setLastPropertyNum(Integer value);
    Integer getLoginIddleDays();
    void setLoginIddleDays(Integer value);
    Integer getMaxLoginAttempt();
    void setMaxLoginAttempt(Integer value);
    Integer getMaxTrades();
    void setMaxTrades(Integer value);
    Boolean isPasswordCheckDigit();
    void setPasswordCheckDigit(Boolean value);
    Boolean isPasswordCheckSpecialChar();
    void setPasswordCheckSpecialChar(Boolean value);
    Integer getPasswordMinLength();
    void setPasswordMinLength(Integer value);
    Boolean isPasswordChangeDue();
    void setPasswordChangeDue(Boolean value);
    Integer getThresholdDays();
    void setThresholdDays(Integer value);
    String getUpdateUser();
    void setUpdateUser(String value);
    Integer getVersion();
    void setVersion(Integer value);
    List<String> getProcessingOrg();
    String getName();
    void setName(String value);
}
package com.mycompany.user;

import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "userType", propOrder = { "fullName", "description", "lastPropertyNum", "loginIddleDays",
        "maxLoginAttempt", "maxTrades", "passwordCheckDigit", "passwordCheckSpecialChar", "passwordMinLength",
        "passwordChangeDue", "thresholdDays", "updateUser", "version", "processingOrg" })
public class UserTypeImpl implements UserType {

    protected String fullName;
    protected String description;
    protected Integer lastPropertyNum;
    protected Integer loginIddleDays;
    protected Integer maxLoginAttempt;
    protected Integer maxTrades;
    protected Boolean passwordCheckDigit;
    protected Boolean passwordCheckSpecialChar;
    protected Integer passwordMinLength;
    protected Boolean passwordChangeDue;
    protected Integer thresholdDays;
    protected String updateUser;
    protected Integer version;
    protected List<String> processingOrg;
    @XmlAttribute
    protected String name;

    @Override
    public String getFullName() {
        return fullName;
    }

    @Override
    public void setFullName(String value) {
        fullName = value;
    }

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public void setDescription(String value) {
        description = value;
    }

    @Override
    public Integer getLastPropertyNum() {
        return lastPropertyNum;
    }

    @Override
    public void setLastPropertyNum(Integer value) {
        lastPropertyNum = value;
    }

    @Override
    public Integer getLoginIddleDays() {
        return loginIddleDays;
    }

    @Override
    public void setLoginIddleDays(Integer value) {
        loginIddleDays = value;
    }

    @Override
    public Integer getMaxLoginAttempt() {
        return maxLoginAttempt;
    }

    @Override
    public void setMaxLoginAttempt(Integer value) {
        maxLoginAttempt = value;
    }

    @Override
    public Integer getMaxTrades() {
        return maxTrades;
    }

    @Override
    public void setMaxTrades(Integer value) {
        maxTrades = value;
    }

    @Override
    public Boolean isPasswordCheckDigit() {
        return passwordCheckDigit;
    }

    @Override
    public void setPasswordCheckDigit(Boolean value) {
        passwordCheckDigit = value;
    }

    @Override
    public Boolean isPasswordCheckSpecialChar() {
        return passwordCheckSpecialChar;
    }

    @Override
    public void setPasswordCheckSpecialChar(Boolean value) {
        passwordCheckSpecialChar = value;
    }

    @Override
    public Integer getPasswordMinLength() {
        return passwordMinLength;
    }

    @Override
    public void setPasswordMinLength(Integer value) {
        passwordMinLength = value;
    }

    @Override
    public Boolean isPasswordChangeDue() {
        return passwordChangeDue;
    }

    @Override
    public void setPasswordChangeDue(Boolean value) {
        passwordChangeDue = value;
    }

    @Override
    public Integer getThresholdDays() {
        return thresholdDays;
    }

    @Override
    public void setThresholdDays(Integer value) {
        thresholdDays = value;
    }

    @Override
    public String getUpdateUser() {
        return updateUser;
    }

    @Override
    public void setUpdateUser(String value) {
        updateUser = value;
    }

    @Override
    public Integer getVersion() {
        return version;
    }

    @Override
    public void setVersion(Integer value) {
        version = value;
    }

    @Override
    public List<String> getProcessingOrg() {
        if (processingOrg == null) {
            processingOrg = new ArrayList<>();
        }
        return processingOrg;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String value) {
        name = value;
    }

}

There are 2 ObjectFactory classes, 2nd one is in impl package :

package com.mycompany.user;

import javax.xml.bind.annotation.XmlRegistry;

import com.mycompany.user.impl.UserListImpl;
import com.mycompany.user.impl.UserTypeImpl;

@XmlRegistry
public class ObjectFactory {

    public ObjectFactory() {
    }

    public UserType createUserType() {
        return new UserTypeImpl();
    }

    public UserList createUserList() {
        return new UserListImpl();
    }

}
package com.mycompany.user.impl;

import javax.xml.bind.annotation.XmlRegistry;

@XmlRegistry
public class ObjectFactory {

    public ObjectFactory() {
    }

    public UserTypeImpl createUserType() {
        return new UserTypeImpl();
    }

    public UserListImpl createUserList() {
        return new UserListImpl();
    }
}

In com.mycompany.user, the jaxb.properties indicates a user-defined context factory:

javax.xml.bind.context.factory=com.mycompany.user.impl.JAXBContextFactory

The javadoc of the source code explains how the 2 ObjectFactory classes are used to handle interface/implementation model classes :

https://github.com/javaee/jaxb-v2/blob/master/jaxb-ri/xjc/src/main/java/com/sun/tools/xjc/runtime/JAXBContextFactory.java

This code is only used when XJC generates interfaces/implementations.

The trick to make this work is two ObjectFactory classes that we generate in the interface/implementation mode.

The public ObjectFactory follows the spec, and this is the one that's exposed to users. The public ObjectFactory refers to interfaces, so they aren't directly usable by a JAXB 2.0 implementation.

The private one lives in the impl package, and this one is indistinguishable from the ObjectFactory that we generate for the value class generation mode. This private ObjectFactory refers to implementation classes, which are also indistinguishable from value classes that JAXB generates.

All in all, the private ObjectFactory plus implementation classes give a JAXB provider an illusion that they are dealing with value classes that happens toimplement some interfaces.

In this way, the JAXB RI can provide the portability even for theinterface/implementation generation mode.

As far as I understand it, the error below suggests that JAXB runtime is unable to guess the proper implementation of the interface as it doesn't know which one it should use between many possible interfaces.

Below the error obtained when running in Eclipse in an isolated project:

javax.xml.bind.JAXBException: Provider com.sun.xml.bind.v2.ContextFactory could not be instantiated: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
com.mycompany.user.UserList is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserList does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory

 - with linked exception:
[com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
com.mycompany.user.UserList is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserList does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory
]
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:146)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:335)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:431)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:394)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:298)
    at com.mycompany.user.ImportUser.unmarshall(ImportUser.java:18)
    at com.mycompany.user.ImportUserTest.testUnmarshall(ImportUserTest.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 4 counts of IllegalAnnotationExceptions
com.mycompany.user.UserList is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserList does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserList
        at public com.mycompany.user.UserList com.mycompany.user.ObjectFactory.createUserList()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType is an interface, and JAXB can't handle interfaces.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory
com.mycompany.user.UserType does not have a no-arg default constructor.
    this problem is related to the following location:
        at com.mycompany.user.UserType
        at public com.mycompany.user.UserType com.mycompany.user.ObjectFactory.createUserType()
        at com.mycompany.user.ObjectFactory

    at com.sun.xml.bind.v2.runtime.IllegalAnnotationsException$Builder.check(IllegalAnnotationsException.java:102)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getTypeInfoSet(JAXBContextImpl.java:472)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:302)
    at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1140)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121)
    at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:202)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:171)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:131)
    ... 29 more

Solution

  • I had jaxb.properties in src/main/java/com/mycompany/user that had to be moved to src/main/resources/com/mycompany/user. Indeed, Gradle ignores the properties that are in src/main/java and does not copy them to the build folder.