Interface mapping with Moxy in a binding.xml not working with multiple interfaces?

877 views Asked by At

I am trying to use Moxy v2.5.2 binding.xml to map a class as an alternative to annotations as I can't put annotations in a third party parent class.

EDIT: I modified this part below after feedback from Blaise

I don't seem to be able to map an interface, due to the parent interfaces.

The java code snippet:

    public final class CmsContent {
        private ContentItemGroup group;
   .... getter/setters
 }

public class ContentItemGroupDefault extends CoreKeyBase implements ContentItemGroup {
    private String bla;
   .... getter/setters
}

The binding file:

<xml-bindings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" 
    package-name="com.core.domain" xml-mapping-metadata-complete="true" xml-accessor-type="NONE">

    <java-types>
        <java-type name="CmsContent" super-type="java.lang.Object" xml-accessor-type="NONE" >
            <xml-root-element name="content" />
            <java-attributes>
                <xml-element java-attribute="group" name="group" type="com.core.domain.impl.ContentItemGroupDefault" />
            </java-attributes>
        </java-type>

        <java-type name="ContentItemGroupDefault" super-type="java.lang.Object" xml-accessor-type="NONE">
            <java-attributes>
                <xml-attribute java-attribute="id" name="key"  /> <!-- from parent class -->
                <xml-transient java-attribute="name" />
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

And the code to marshal it:

final Map<String, Object> properties = new HashMap<String, Object>();
final InputStream bindings = CmsContent.class.getResourceAsStream("jaxb-binding.xml");
properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);
final Class<?>[] classes = new Class<?>[] { CmsContent.class};
final JAXBContext context = JAXBContext.newInstance(classes, properties);
final Marshaller marshaller = context.createMarshaller();

final CmsContent content = createCmsContent();
marshaller.marshal(content, System.out);

The famous Exception:

The java interface com.core.domain.ContentItemGroup can not be mapped by JAXB as it has multiple mappable parent interfaces. Multiple inheritence is not supported

Note: the example below of @Blaise, runs well, thanks for that, but that example has not multiple parent classes like in my case, which causes the exception, even do in the binding xml I use the super-type attribute.

After debugging the Moxy code the exception occurs when the Moxy class "AnnotationsProcessor" scans all properties of CmsContent and it tried to discover if the property ContentItemGroup in CmsContent is a Collection, method: Helper.isCollectionType(..).

In the Moxy method JavaClassImpl.getSuperclass(..), it will loop through all parent classes of the interface ContentItemGroup property and will fail because the parent class doesn't start with "java" or "javax". Note: the interface ContentItemGroup extends 2 interfaces.

Why does it fail and doesn't it use the defined super-type in the binding xml ?

It works in case ContentItemGroup does not extend from any interface or in case it extends from a single interface (that also counts for all extends parent interfaces). In that case it will pass the method below with success. However, removing the parent interfaces isn't an option :(

Other problem: BTW: when it works, how to output properties from the ContentItemGroupDefault parent class as xml attribute ? In the above example, the "id" from the parent class does't appear in the xml. In case I remove the "super-type" attribute all the parent class properties appear in the output, even when I mark them as as "transient". What am I doing wrong?

The method JavaClassImpl.getSuperclass(..):

public JavaClass getSuperclass() {
    if(this.superClassOverride != null) {
        return this.superClassOverride;
    }
    if(jClass.isInterface()) {
        Class[] superInterfaces = jClass.getInterfaces();
        if(superInterfaces != null) {
            if(superInterfaces.length == 1) {
                return javaModelImpl.getClass(superInterfaces[0]);
            } else {
                Class parent = null;
                for(Class next:superInterfaces) {
                    if(!(next.getName().startsWith("java.") || next.getName().startsWith("javax."))) {
                        if(parent == null) {
                            parent = next;
                        } else {
                            throw JAXBException.invalidInterface(jClass.getName());
                        }
                    }
                }
                return javaModelImpl.getClass(parent);
            }
        }
    }
    return javaModelImpl.getClass(jClass.getSuperclass());
}

UPDATE:

@BLAISE:    <java-type name="AbstractCmsContent" xml-transient="true">

When changing the binding xml ContentItemGroupDefault to (adding the xml-transient):

<java-type name="ContentItemGroupDefault" super-type="java.lang.Object" xml-accessor-type="NONE" xml-transient="true">

I do get the following exception:

Exception Description: A descriptor for class com.core.cms.moxy.ContentItemGroupDefault was not found in the project.  For JAXB, if the JAXBContext was bootstrapped using TypeMappingInfo[] you must call a marshal method that accepts TypeMappingInfo as an input parameter.

Which I don't understand. Also when adding the ContentItemGroupDefault to the JAXContent, has no effect. When adding the "xml-transient" property to the CmsContent mapping, it will result in the same exception, but then concering the CmsContent class.

UPDATE: In the meantime I fixed it by using Woodstox directly, which costs my about 4 hours including testing. After days of frustration I don't seem to get Moxy to perform the simple mapping. Even if I do make mistakes, I think Moxy can be made more friendly/accessible for these simple kind of simple meta mappings. When adding multiple interfaces to the the PhoneNumber interface of @Blaise it's example below, I do get the same "Multiple interface support" error. I wanted to use Moxy as I use it in other places of the platform that concerns thousands of generated jaxb classes that work well. However, a simple mapping case seems hard.. :(

1

There are 1 answers

7
bdoughan On

When you have an interface fronted model, it's really the backing implementation classes that you are mapping. Below is a link to an example I have that demonstrates how this is done with annotations.

For that linked example below would be the corresponding mapping document:

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="blog.interfaces">
    <java-types>
        <java-type name="CustomerImpl">
            <xml-root-element name="customer"/>
            <java-attributes>
                <xml-element java-attribute="address" type="blog.interfaces.AddressImpl"/>
                <xml-element java-attribute="phoneNumbers" name="phone-number" type="blog.interfaces.PhoneNumberImpl"/>
            </java-attributes>
        </java-type>
        <java-type name="PhoneNumberImpl">
            <java-attributes>
                <xml-value java-attribute="value"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

UPDATE

Thanks @Blaise. With your feedback I isolated the problem further and updated the above code example. Moxy doesn't like multiple parent interfaces (your code example works fine, but has no multiple interfaces)... Please feedback?

The multiple parent interface issue should only occur if you are trying to treat the interface as a mapped thing. My recommendation would be to not do that. Is there anywhere you haven't overridden the a property to be the actual implementation type?

I haven't been able to reproduce the error that you are seeing. One thing to note though is that when you do the following you are really telling MOXy that the super class of this class is Object. As far as MOXy is concerned it will not process the real super class of CmsContent.

<java-type name="CmsContent" super-type="java.lang.Object" xml-accessor-type="NONE" >

If you just want to treat the super class of CmsContent as simply unmapped and have the properties treated as properties of the subclasses then you would mark that class as @XmlTransient. This is done in the XML metadata like:

<java-type name="AbstractCmsContent" xml-transient="true">