Override JAXB binding in non-modifyable domain Jav

2019-07-20 22:11发布

问题:

I have spent the whole day trying to figure out this problem (including extensive searching on this site), but I can't find an answer to my problem. I am trying to achieve this:

  • Convert between XML and some existing Java objects that I have no control over
  • Names of elements in the resulting/source XML differ from names of properties of the Java classes
  • I am limited to jaxb-2.0
  • I may introduce a wrapping class that can contain the annotations

Let me show you an example of what I'm trying to achieve. Let's assume that the Java class I have no control over looks like this:

public class TopNoControlClass {

    private BottomNoControlClass bottomNoControlObject;

    public TopNoControlClass(BottomNoControlClass bottomNoControlObject) {
        super();
        this.bottomNoControlObject = bottomNoControlObject;
    }

    public BottomNoControlClass getBottomNoControlObject() {
        return bottomNoControlObject;
    }
    public void setBottomNoControlObject(BottomNoControlClass bottomNoControlObject) {
        this.bottomNoControlObject = bottomNoControlObject;
    }
}

And the referenced class:

public class BottomNoControlClass {

    private String foo;

    public BottomNoControlClass(String foo) {
        super();
        this.foo = foo;
    }

    public String getFoo() {
        return foo;
    }
    public void setFoo(String foo) {
        this.foo = foo;
    }
}

And imagine I want to get this out of the marshalling:

<?xml version="1.0" encoding="UTF-8"?>
<Top>
    <Bottom>
        <bar>XXX</bar>
    </Bottom>
</Top>

The <Top> would map to the TopNoControlClass and <Bottom> Bottom would map to the BottomNoControlClass and <bar> would map to the foo property of BottomNoControlClass.

In order to do the above, I would be comfortable with creating an external XML binding that would state the mappings, but I can't figure a way to use that external binding file in runtime. All the examples I've seen so far only used external XML bindings at generation time (i.e. as a parameter to xjc).

I also wouldn't have a problem with introducing a wrapper class that would override the class names and class property names for the classes it would refer (i.e. TopNoControlClass and BottomNoControlClass). It would be easy to construct the JAXBContext with that class and let JAXB do the rest. But I can't figure out how that annotation should look like.

Any help would be greatly appreciated

Jaroslav

回答1:

Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.

In the MOXy implementation of JAXB 2 (JSR-222) we offer an external mapping document for exactly this use case.

External Metadata (oxm.xml)

Below is what the mapping document would look like for your use case. For the purpose of this example put your model classes in a package called forum13318677. For more information see: http://blog.bdoughan.com/2010/12/extending-jaxb-representing-annotations.html

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="forum13318677">
    <java-types>
        <java-type name="TopNoControlClass">
            <xml-root-element name="Top"/>
            <xml-type factory-class="forum13318677.Factory" factory-method="createTopNoControlClass"/>
            <java-attributes>
                <xml-element java-attribute="bottomNoControlObject" name="Bottom"/>
            </java-attributes>
        </java-type>
        <java-type name="BottomNoControlClass">
            <xml-type factory-class="forum13318677.Factory" factory-method="createBottomNoControlClass"/>
            <java-attributes>
                <xml-element java-attribute="foo" name="bar"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Factory

Since your model classes did not have 0-argument constructors I needed to create a factory class (see: http://blog.bdoughan.com/2011/06/jaxb-and-factory-methods.html). This class was configured in the XML representation of the @XmlType annotation.

package forum13318677;

public class Factory {

    public TopNoControlClass createTopNoControlClass() {
        return new TopNoControlClass(null);
    }

    public BottomNoControlClass createBottomNoControlClass() {
        return new BottomNoControlClass(null);
    }

}

jaxb.properties

To specify MOXy as your JAXB (JSR-222) provider you need to include a file called jaxb.properties in the same package as your domain classes with the following entry (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html).

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

Demo

The demo code below demonstrates how to leverage MOXy's external mapping document. Note that even though MOXy is used as the JAXB provider there are no compile time dependencies on MOXy.

package forum13318677;

import java.io.File;
import java.util.*;
import javax.xml.bind.*;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String,Object>();
        properties.put("eclipselink.oxm.metadata-source", "forum13318677/oxm.xml");
        JAXBContext jc = JAXBContext.newInstance(new Class[] {TopNoControlClass.class}, properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        File xml = new File("src/forum13318677/input.xml");
        TopNoControlClass object = (TopNoControlClass) unmarshaller.unmarshal(xml);

        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.marshal(object, System.out);
    }

}