May 27, 2011

Overriding JAXB's "Name Mangling" Algorithm with EclipseLink MOXy

JAXB has well established rules for converting Java names to XML names.  If you want to override the default name you can always do so through the use of annotations.  Where this can become burdensome is if your names follow common rules (such as make everything upper case).  In the upcoming EclipseLink 2.3 release we have added a new MOXy JAXB feature that allows you to override the default naming algorithm.   You can try this out today using a nightly download:


Overriding the Java Name to XML Name Algorithm

XMLNameTransformer

We will create an implementation of XMLNameTransformer to provider our own naming  algorithm to MOXy.  This algorithm can be described as follows:

  1. Root Element Name
    We will use the unqualified class name as our root element name.
  2. Type Name
    The same algorithm as root element name plus "Type" appended to the end.
  3. Element Name
    The name will be lower case with work breaks represented by '-'.  Note:  A capital letter in the original name represents the start of a new word.
  4. Attribute Name
    The original name converted to upper case.

package blog.namemangling;

import org.eclipse.persistence.oxm.XMLNameTransformer;

public class NameMangler implements XMLNameTransformer {

    public String transformRootElementName(String name) {
        return name.substring(name.lastIndexOf('.') + 1);
    }

    public String transformTypeName(String name) {
        return transformRootElementName(name) + "Type";
    }

    public String transformElementName(String name) {
        StringBuilder strBldr = new StringBuilder();
        for(char character : name.toCharArray()) {
            if(Character.isUpperCase(character)) {
                strBldr.append('-');
                strBldr.append(Character.toLowerCase(character));
            } else {
                strBldr.append(character);
            }
         }
        return strBldr.toString();
    }

    public String transformAttributeName(String name) {
        return name.toUpperCase();
    }

}

@XmlNameTransformer

Our implementation of the naming algorithm is provided via the @XmlNameTransformer annotation which can be specified at the type or package level:

@XmlNameTransformer(NameMangler.class)
package blog.namemangling;

import org.eclipse.persistence.oxm.annotations.XmlNameTransformer;

Java Model

The following domain model will be used in this post.  To save space the accessors have been omitted.

Customer

package blog.namemangling;

import javax.xml.bind.annotation.*;

@XmlRootElement
@XmlType(propOrder={"fullName", "shippingAddress"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlAttribute
    private long id;

    private String fullName;
    
    private Address shippingAddress;

}

Address

package blog.namemangling;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
public class Address {

    @XmlAttribute
    private String type;

    private String street;

}

Demo Code

package blog.namemangling;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;

public class Demo {

    public static void main(String[] args) throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Customer.class);

        Customer customer = new Customer();
        customer.setId(123);
        customer.setFullName("Jane Doe");

        Address address = new Address();
        address.setType("residential");
        address.setStreet("1 Any Street");
        customer.setShippingAddress(address);

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

}

Default Naming Rules

Using JAXB's default naming algorithm our object model will be converted to XML that looks like the following:

<?xml version="1.0" encoding="UTF-8"?>
<customer id="123">
    <fullName>Jane Doe</fullName>
    <shippingAddress type="residential">
        <street>1 Any Street</street>
    </shippingAddress>
</customer>

Overridden Naming Rules

By leveraging our customized naming algorithm we can get the following output without specifying any additional metadata on our domain classes:

<?xml version="1.0" encoding="UTF-8"?>
<Customer ID="123">
   <full-name>Jane Doe</full-name>
   <shipping-address TYPE="residential">
      <street>1 Any Street</street>
   </shipping-address>
</Customer>

Necessary Mapping

Without overriding the naming algorithm, we would have had to specify additional JAXB metadata to get the desired XML output:

Customer

package blog.namemangling;

import javax.xml.bind.annotation.*;

@XmlRootElement(name="Customer")
@XmlType(
        name="CustomerType", 
        propOrder={"fullName", "shippingAddress"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlAttribute(name="ID")
    private long id;

    @XmlElement(name="full-name")
    private String fullName;
    
    @XmlElement(name="shipping-address")
    private Address shippingAddress;

}

Address

package blog.namemangling;

import javax.xml.bind.annotation.*;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name="AddressType")
public class Address {

    @XmlAttribute(name="TYPE")
    private String type;

    private String street;

}

Further Reading

If you enjoyed this post you may also be interested in:
Other articles related to new MOXy features in EclipseLink 2.3:

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.