December 10, 2010

Extending JAXB - Representing Metadata as XML

In JAXB (JSR-222) metadata is applied to Java classes via standard annotations. There are times when using annotatations is not practical, and an alternate mechanism is required.  In this post I'll demonstrate how EclipseLink JAXB (MOXy) can leverage XML to represent the JAXB metadata.

An XML metadata representation is useful when:
  • You cannot modify the domain model (it may come from a 3rd party).
  • You do not want to introduce compile dependencies on JAXB APIs (if you are using a version of Java prior to Java SE 6).
  • You want to apply multiple JAXB mappings to a domain model (you are limited to one representation with annotations).
  • Your object model already contains so many annotations from other technologies that adding more would make the class unreadable.


Mapping File (binding.xml)

The mapping file contains the same information as the JAXB annotations.  Like with annotations you only need to specify metadata to override default behavior.

<?xml version="1.0"?>
<xml-bindings
    xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
    package-name="blog.bindingfile">
    <xml-schema 
        namespace="http://www.example.com/customer" 
        element-form-default="QUALIFIED"/>
    <java-types>
        <java-type name="Customer">
            <xml-root-element/>
            <xml-type prop-order="firstName lastName address phoneNumbers"/>
            <java-attributes>
                <xml-element java-attribute="firstName" name="first-name"/>
                <xml-element java-attribute="lastName" name="last-name"/>
                <xml-element java-attribute="phoneNumbers" name="phone-number"/>
            </java-attributes>
        </java-type>
        <java-type name="PhoneNumber">
            <java-attributes>
                <xml-attribute java-attribute="type"/>
                <xml-value java-attribute="number"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

Domain Model

The following domain model will be used in this example.  Because the JAXB metadata is represented as XML, no annotations are used on the classes.

Customer 

package blog.bindingfile;

import java.util.List;
 
public class Customer {

    private String firstName;
    private String lastName;
    private Address address;
    private List<PhoneNumber> phoneNumbers;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

}

Address 

package blog.bindingfile;

public class Address {

    private String street;

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

}

PhoneNumber 

package blog.bindingfile;

public class PhoneNumber {
 
    private String type;
    private String number;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

}

XML (input.xml)

The following XML will be used in this example:

<?xml version="1.0" encoding="UTF-8"?>
<customer xmlns="http://www.example.com/customer">
    <first-name>Jane</first-name>
    <last-name>Doe</last-name>
    <address>
        <street>123 A Street</street>
    </address>
    <phone-number type="work">555-1111</phone-number>
    <phone-number type="cell">555-2222</phone-number>
</customer>

Demo Code

The XML mapping file is passed in via the properties parameter when the JAXBContext is instantiated.

package blog.bindingfile;
 
import java.io.File;
import java.util.*;
import javax.xml.bind.*;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

    public static void main(String[] args) throws Exception {
        Map<String, Object> properties = new HashMap<String, Object>(1);
        properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "blog/bindingfile/binding.xml");
        JAXBContext jc = JAXBContext.newInstance("blog.bindingfile", Customer.class.getClassLoader() , properties);

        Unmarshaller unmarshaller = jc.createUnmarshaller();
        Customer customer = (Customer) unmarshaller.unmarshal(new File("src/blog/bindingfile/input.xml"));

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

}

Specifying the MOXy JAXB Implementation (jaxb.properties)

In order to use this extension, you must use MOXy as your JAXB implementation.  To enable MOXy simply add a file named jaxb.properties in the same package as your domain classes with the following entry:

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

If you enjoyed this post you may also be interested in:
 

42 comments:

  1. Very helpful post! I've got it working. Do you have an example of how to plug this into a web-service stack? I have a SOAP-based web service implemented as an EJB3 seam component deployed on JBoss. Where would I put the above code to get the marshalling/unmarshalling of the request to my object model so that it uses the external mappings file?

    ReplyDelete
  2. Hi Frances,

    Most JAX-WS implementations are dependent on a particular JAXB implementation. If they do not support plugging in alternate JAXB implementations then you will need to create a service that interacts directly with XML.

    If you are using RESTful services via JAX-RS, there is a ContextResolver mechanism that makes specifying the JAXBContext very easy.

    -Blaise

    ReplyDelete
  3. Thanks for the quick reply Blaise! Do you know which JAX-WS implementations support EclipseLink MOXy?

    ReplyDelete
    Replies
    1. EclipseLink JAXB (MOXy) is used by the JAX-WS implementation in WebLogic as of version 12.1.1 (see: EclipseLink MOXy is the JAXB Provider in WebLogic Server 12c). MOXy can also be used with the JAX-WS implementation in GlassFish as of version 3.1.2 (see: GlassFish 3.1.2 is Full of MOXy (EclipseLink JAXB)).

      Delete
  4. Hi Frances,

    We are currently working with some JAX-WS implementations. I will post updates when they are available.

    -Blaise

    ReplyDelete
  5. Is there a way to generate the binding.xml in Moxy or other implementations of JAXB?

    ReplyDelete
  6. Hi,
    Can i use MOXy in my WebService project (i am using JAXWS2.2.1-20100617) to overcome the inheritance issue stated at http://stackoverflow.com/questions/4661263/jaxb-2-x-how-to-override-an-xmlelement-annotation-from-parent-class-mission-i
    .

    I have the same problem just like bzero.MOXy works perfectly in a standalone application.But i think i'can make it work with my WebService Application ?

    ReplyDelete
  7. Hi elcinsumerkan,

    We are currently working with a couple of JAX-WS implementations to provide support for using EclipseLink MOXy as the binding layer. I will post the information on my blog when these implementatations are ready to release.

    For now you can use EclipseLink MOXy to handle the XML if you have a Provider based Web Service. I will try to blog about this topic soon.

    -Blaise

    ReplyDelete
  8. I am using the CXF Stack on JBoss EAP 5.1.

    I have the eclipselink.jar in the classpath and the jaxb.properties in the package.

    @XmlCDATA is not working for me. Im not sure why so I created the eclipselink-oxm.xml file.

    Where do I put that xml file with the bindings in it so that the JAX-WS generated services use it when Marshalling the Models to XML? Thank you.

    ReplyDelete
  9. Hello David,

    If you are using JAX-RS with the CXF Stack then the following post should help:

    - MOXy's XML Metadata in a JAX-RS Service

    -Blaise

    ReplyDelete
  10. Hi David,

    Not all JAXB implementations support plugging in alternate JAXB implementations. I'm working with a couple now to add support for MOXy, but unfortunately JBoss' is not one of them.

    Ideally JAX-WS will be enhanced to offer the same type of pluggability offered by JAX-RS:
    - MOXy's XML Metadata in a JAX-RS Service

    -Blaise

    ReplyDelete
  11. HI Blaise:

    Is there a way to auto generate bindings.xml or plugin available in eclipse?

    ReplyDelete
  12. There currently isn't a way to auto generate a bingings.xml. Were you thinking of having one generated from an XML schema instead of having annotations on the classes?

    -Blaise

    ReplyDelete
  13. Thanks for reply Blaise. I have xsd and want to use 'externalized meta data' to marshall / unmarshal.

    Was just curious if bindings can be auto generated and a developer can edit it to customize it to the needs. Seems manual way is the way to go now..no problem.

    regards and many thanks.

    ReplyDelete
  14. Hi, Blaise,

    Is it possible to get rid of namespace "xmlns" in marshalled xml? I just want to have a "pure" xml generated. Thanks,

    Foster

    ReplyDelete
  15. Hi Foster,

    Your JAXB implementation will only write an "xmlns" declaration if you have included namespace information in your mapping. Below is an example without any namespace declarations:
    - http://wiki.eclipse.org/EclipseLink/Examples/MOXy/GettingStarted/TheBasics

    The following article may help you determine where you may be specifying namespace information:
    - JAXB & Namespaces

    -Blaise

    ReplyDelete
  16. Thanks Blaise. I'm actually using "xml-bindings.xml" for marshaling my objects, and it's always give me the xmlns, which I don't want at all - any advise? Thanks,

    Foster

    ReplyDelete
  17. Hi Foster,

    In this example if your remove the <xml-schema> tag from the bindings file, then the marshalled XML document will not be namespace qualified.

    -Blaise

    ReplyDelete
  18. I did, but still get:
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    ReplyDelete
  19. Do you have inheritance in your object model? Also which version of EclipseLink are you using?

    -Blaise

    ReplyDelete
  20. No, I don't have any inheritance, and use EclipseLink 2.3.0.

    ReplyDelete
  21. Could you provide me more details about your model via my Contact Me page, so that I can better recreate your setup regarding the xsi namespace declaration?

    -Blaise

    ReplyDelete
  22. Hi Frances,

    It took longer than I thought it would, but EclipseLink JAXB (MOXy) is now the default JAXB implementation in WebLogic 12c, and can be used to create JAX-WS web services:
    - EclipseLink MOXy is the JAXB Provider in WebLogic Server 12c

    -Blaise

    ReplyDelete
  23. That's great news! Nice work! Looking forward to trying it out. I know you mentioned JBoss-AS wasn't in your plans but has that since changed?

    ReplyDelete
    Replies
    1. Hi Frances,

      No news on JBoss and JAX-WS support for MOXy. I am happy to announce that GlassFish 3.1.2 will support MOXy as a JAXB provider in its JAX-WS implementation. I will post an example soo.

      -Blaise

      Delete
  24. Hi,
    Examples from this post seem not to work at all with the latest 2.3.3-SNAPSHOT.
    First, there is the exception: "The property or field lastName was specified in propOrder but is not a valid property".
    Then after removing the prop-order definition this is the output:
    [EL Warning]: 2012-04-13 15:24:58.39--Ignoring attribute [firstName] on class [mpol.Customer] as no Property was generated for it.
    [EL Warning]: 2012-04-13 15:24:58.398--Ignoring attribute [lastName] on class [mpol.Customer] as no Property was generated for it.
    [EL Warning]: 2012-04-13 15:24:58.399--Ignoring attribute [phoneNumbers] on class [mpol.Customer] as no Property was generated for it.
    [EL Warning]: 2012-04-13 15:24:58.399--Ignoring attribute [type] on class [mpol.PhoneNumber] as no Property was generated for it.
    [EL Warning]: 2012-04-13 15:24:58.399--Ignoring attribute [number] on class [mpol.PhoneNumber] as no Property was generated for it.
    <?xml version="1.0" encoding="UTF-8"?>
    <customer xmlns="http://www.example.com/customer"/>

    ReplyDelete
    Replies
    1. Hi,

      This example requires that your domain model include the accessor methods (get/set methods). I originally omitted them from the post to save space, but have just added them back in. I apologize for any confusion.

      -Blaise

      Delete
  25. I see. But then this is a very surprising feature, given that when using annotations they can be added on fields without creating getters/setters.

    ReplyDelete
    Replies
    1. Hi,

      You specify in the mapping file that field access should be used by specifying xml-accessor-type="FIELD" as an attribute on the java-type element.

      -Blaise

      Delete
    2. But xml-accessor-type="FIELD" sets the default to map all fields, like @XmlAccessorType(FIELD).
      What you can do with annotations, and apparently not with XML, is to a map a specific field that has no getter/setter, without changing the default accessor type.

      One would hope that the XML metadata is able to work in more or less every case that can be mapped with annotations. But it just seems not to be the case here.

      Delete
    3. This is an issue we are going to address. You can track our progress on this issue with the following bug (which I believe you entered):
      - http://bugs.eclipse.org/376831

      -Blaise

      Delete
  26. Can we convert jibx bindings in xml in to the json binding(xslt)

    ReplyDelete
    Replies
    1. That should be possible. Are you volunteering?

      -Blaise

      Delete
  27. What is the correct location to place the jaxb.properties file? I'm seeing: javax.xml.bind.JAXBException: "com.x.y.z" doesnt contain ObjectFactory.class or jaxb.index

    I have the file in the com.x.y.z folder and it works in local Eclipse/Tomcat/maven setup but when I deploy to a server, I see the above exception. The code that causes this is:
    jc = JAXBContext.newInstance("com.x.y.z", ZMappingController.class.getClassLoader(), properties);

    The ZMappingController.java is in a different package (com.x.y.a). Also, is it possible to place the properties file in a common folder like /src/main/resources/

    ReplyDelete
  28. Thanks for your quick reply!. It works. I was trying to the same as I was waiting for your reply.

    ReplyDelete
  29. Hi Blaise..
    One quick question
    Regarding your first point
    1) You cannot modify the domain model (it may come from a 3rd party).

    If it is coming from thirdparty, how can they already be equipped with ObjectFactory.class or jaxb.index or jaxb.properties file?

    ReplyDelete
  30. Hi Blaise.

    E.g.
    --------------------------------------------------------------
    // Model:
    public class Document {
    private String name;
    private Date date;

    getters and setters ...
    }

    Input XML file:



    Some name


    23.11.2012



    Using annotations it would be:

    @XmlPath("document/foo/name/text()")
    private String name;

    @XmlPath("document/bar/date/text()")
    @XmlJavaTypeAdapter(SomeAdapter.class)
    private Date date;
    --------------------------------------------------------------

    Could you please show (or provide link) how to represent @XmlPath and @XmlJavaTypeAdapter in xml binding. I need it for unmarshalling.

    Thanks in advance.

    ReplyDelete
    Replies
    1. Check out example Example 2-11 in the MOXy User Guide for an example of how to map this use case:
      - http://www.eclipse.org/eclipselink/documentation/2.4/moxy/runtime003.htm#CACHCHAE

      -Blaise

      Delete
    2. Thanks.
      I've checked the official docs, now everything works without annotations as well ))

      MOXy is so nice, thank you for your work!

      Delete
  31. Hi,

    Is there a way I can map the same object to different JSON/XML response without having the need to create a different objects.

    ReplyDelete
    Replies
    1. Definitely. This is one of our primary use cases. Check out the following example where a single object model is mapped to both the Google and Yahoo weather services:
      - Mapping Objects to Multiple XML Schemas - Weather Example

      Delete