Search code examples
jaxbeclipselinkjaxb2moxy

Inheritance mapping throwing an exception with MOXy


I followed the second option mentioned in JAXB inheritance in MOXY to map my parent class and child class listed below. MOXy is throwing the below exception and not sure what the issue is

Parent class

public class UnitedStatesAddressData extends AbstractAddress implements UnitedStatesAddress, Serializable, Cloneable
{
    private String primaryAddress;

    public String getPrimaryAddress()
    {
        return primaryAddress;
    }

    public void setPrimaryAddress(final String primaryAddress)
    {
        this.primaryAddress = primaryAddress;
    }
}

Child Class

public class TokenizedUnitedStatesAddressData extends UnitedStatesAddressData implements TokenizedUnitedStatesAddress,
        CloneableAddress
{
    private String houseNumber;

    private String preDirectional;

    private String streetName;

    private String streetType;

//getters and setters ignored
}

external binding file

<?xml version="1.0" encoding="UTF-8"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/oxm http://www.eclipse.org/eclipselink/xsds/eclipselink_oxm_2_4.xsd"
   version="2.4" package-name="com.abc.ic.domain.impl.country.us" xml-accessor-type="PROPERTY">
   <xml-schema element-form-default="QUALIFIED" namespace="http://xml.abc.com/XMLSchema/InterConnect">
      <!-- Do not specify 'prefix'. We doesn't want the namespace prefix in our instance documents as they increase the payload size. -->
      <xml-ns namespace-uri="http://xml.abc.com/XMLSchema/InterConnect" />
   </xml-schema>
   <java-types>
      <java-type name="TokenizedUnitedStatesAddressData">
         <xml-root-element name="USAddress" />
         <xml-type prop-order="preDirectional houseNumber streetName streetType postDirection unitDesignator apartmentNumber primaryAddress secondaryAddress cityName stateAbbreviation zipCode" />
         <java-attributes>
            <xml-element name="StreetPreDirection" java-attribute="preDirectional" />
            <xml-element name="StreetNumber" java-attribute="houseNumber" />
            <xml-element name="StreetName" java-attribute="streetName" />
            <xml-element name="StreetType" java-attribute="streetType" />
            <xml-element name="StreetPostDirection" java-attribute="postDirection" />
            <xml-element name="UnitDesignator" java-attribute="unitDesignator" />
            <xml-element name="UnitNumber" java-attribute="apartmentNumber" />
            <xml-element name="AddressLine1" java-attribute="primaryAddress" />
            <xml-element name="AddressLine2" java-attribute="secondaryAddress" />
            <xml-element name="City" java-attribute="cityName" />
            <xml-element name="State" java-attribute="stateAbbreviation" />
            <xml-element name="PostalCode" java-attribute="zipCode" />
         </java-attributes>
      </java-type>
      <java-type name="UnitedStatesAddressData" xml-transient="true">
         <xml-root-element />
      </java-type>
   </java-types>
</xml-bindings>

Error

javax.xml.bind.JAXBException: 
Exception Description: The property or field primaryAddress is annotated to be transient so can not be included in the proporder annotation.
 - with linked exception:
[Exception [EclipseLink-50009] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.JAXBException
Exception Description: The property or field primaryAddress is annotated to be transient so can not be included in the proporder annotation.]
    at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:908)
    at org.eclipse.persistence.jaxb.JAXBContext.<init>(JAXBContext.java:157)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:170)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:157)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:117)
    at org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(JAXBContextFactory.java:107)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:202)
    at javax.xml.bind.ContextFinder.find(ContextFinder.java:331)
    at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:574)
    at com.abc.ic.platform.sts.domain.transformation.response.JaxbTest.beforeClass(JaxbTest.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: Exception [EclipseLink-50009] (Eclipse Persistence Services - 2.4.0.v20120608-r11652): org.eclipse.persistence.exceptions.JAXBException
Exception Description: The property or field primaryAddress is annotated to be transient so can not be included in the proporder annotation.
    at org.eclipse.persistence.exceptions.JAXBException.transientInProporder(JAXBException.java:225)
    at org.eclipse.persistence.jaxb.compiler.TypeInfo.setProperties(TypeInfo.java:342)
    at org.eclipse.persistence.jaxb.compiler.AnnotationsProcessor.buildTypeInfo(AnnotationsProcessor.java:755)
    at org.eclipse.persistence.jaxb.compiler.AnnotationsProcessor.postBuildTypeInfo(AnnotationsProcessor.java:669)
    at org.eclipse.persistence.jaxb.compiler.XMLProcessor.processXML(XMLProcessor.java:344)
    at org.eclipse.persistence.jaxb.compiler.Generator.<init>(Generator.java:145)
    at org.eclipse.persistence.jaxb.JAXBContext$TypeMappingInfoInput.createContextState(JAXBContext.java:904)
    ... 28 more

UPDATE

I've tested this by removing "prop-order" and this works fine except that I can no longer control the order of the elements in generated XML. I believe this is a bug in the code.


Solution

  • The cause for this is that the child class overrides the method from parent class as sown below but since the parent class has been defined to be xml-transient="true", the below check from TypeInfo is failing.

    child class overriding getPrimaryAddress

    public class TokenizedUnitedStatesAddressData extends UnitedStatesAddressData implements TokenizedUnitedStatesAddress,
            CloneableAddress
    {
    @override
    public String getPrimaryAddress()
    {
       return primaryAddress;
    }
    }
    

    TypeInfo.java offending code

    if (p.isTransient() && propOrderList.contains(p.getPropertyName()))
    {
    throw org.eclipse.persistence.exceptions.JAXBException.transientInProporder(p.getPropertyName());
    }
    

    UPDATE 2

    The issue is not with the AbstractAddress. After some debugging, the issue seems to be with the below logic in the AnnotationProcessor.getPropertyPropertiesForClass(final JavaClass cls, final TypeInfo info, final boolean onlyPublic, final boolean onlyExplicit)

    if ((setMethod == null) && !(hasJAXBAnnotations(getMethod)))
    {
        // if there's no corresponding setter, and not explicitly
        // annotated, don't process
        isPropertyTransient = true;
    }
    

    This method will tag any method that doesn't have both the get/set methods defined as "transient". In my case, I override just the getPrimaryAddress() method in TokenizedUnitedStatesAddressData class and not the corresponding setter. And hence the Annotationprocessor is designating it as a transient property neglecting the fact that this method is being overridden.

    FIX

    The issue is fixed after making sure that the overridden methods are properly handled in the transient check as shown below

    if ((setMethod == null) && !(hasJAXBAnnotations(getMethod)))
    {
        if (!isMethodOverrriden(cls.getQualifiedName(), getMethod.getName()))
        {
            // if there's no corresponding setter, and not explicitly
            // annotated, don't process
            isPropertyTransient = true;
        }
    }
    
    public static boolean isMethodOverrriden(final String classQualifiedName, final String methodName)
        {
            Method myMethod;
            try
            {
                myMethod = Class.forName(classQualifiedName).getMethod(methodName, null);
            }
            catch (Exception e1)
            {
                return false;
            }
    
            Class<?> declaringClass = myMethod.getDeclaringClass();
            if (declaringClass.equals(Object.class))
            {
                return false;
            }
    
            Class<?> superclass = declaringClass.getSuperclass();
            if (superclass == null)
            {
                return false;
            }
            else
            {
                try
                {
                    superclass.getMethod(myMethod.getName(), myMethod.getParameterTypes());
                }
                catch (NoSuchMethodException e)
                {
                    // recursively check all super classes
                    isMethodOverrriden(superclass.getName(), methodName);
                }
                return true;
            }
        }