Monday, February 6, 2012

JAXB - Mapping the unmappable

http://blog.bdoughan.com/2010/07/xmladapter-jaxbs-secret-weapon.html

The XmlAdapter mechanism in JAXB ensures that there is no such thing as an unmappable class (with a little programmatic help). However there appears to be some confusion on how to use XmlAdapter, below is the general concept:

  1. Identify the unmappable class
  2. Create an equivalent class that is mappable
  3. Create an XmlAdapter to convert between unmappable and mappable objects
  4. Specify the XmlAdapter
Part of the problem may be due to the Javadocs on the XmlAdapter class. Below I'll flush out the example used there.


1. Identify the Unmappable Class

 In this example the unmappable class is java.util.Map.

2.  Create an Equivalent Class that is Mappable

Map could be represented by an object (MyMapType), that contained a list of objects with two properties: key and value (MyMapEntryType).

1
2
3
4
5
6
7
8
9
import java.util.ArrayList;
import java.util.List;
 
public class MyMapType {
 
   public List<MyMapEntryType> entry =
      new ArrayList<MyMapEntryType>();
 
}

and
1
2
3
4
5
6
7
8
9
10
11
12
import javax.xml.bind.annotation.XmlValue;
import javax.xml.bind.annotation.XmlAttribute;
 
public class MyMapEntryType {
 
   @XmlAttribute
   public Integer key;
 
   @XmlValue
   public String value;
 
}

3. Create an XmlAdapter to Convert Between Unmappable and Mappable Objects


The XmlAdapter<ValueType, BoundType> class is responsible for converting between instances of the unmappable and mappable classes. Most people get confused between the value and bound types. The value type is the mappable class, and the bound type is the unmappable class.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.bind.annotation.adapters.XmlAdapter;
  
public final class MyMapAdapter extends
  
   XmlAdapter<MyMapType,Map<Integer, String>> {
  
   @Override
   public MyMapType marshal(Map<Integer, String> arg0) throws Exception {
      MyMapType myMapType = new MyMapType();
      for(Entry<Integer, String> entry : arg0.entrySet()) {
         MyMapEntryType myMapEntryType =
            new MyMapEntryType();
         myMapEntryType.key = entry.getKey();
         myMapEntryType.value = entry.getValue();
         myMapType.entry.add(myMapEntryType);
      }
      return myMapType;
   }
  
   @Override
   public Map<Integer, String> unmarshal(MyMapType arg0) throws Exception {
      HashMap<Integer, String> hashMap = new HashMap<Integer, String>();
      for(MyMapEntryType myEntryType : arg0.entry) {
         hashMap.put(myEntryType.key, myEntryType.value);
      }
      return hashMap;
   }
  
}

4. Specify the XmlAdapter

The @XmlJavaTypeAdapter annotation is used to specify the use of the XmlAdapter. Below it is specified on the map field on the Foo class. Now during marshal/unmarshal operations the instance of Map is treated as an instance of MyHashMapType.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Foo {
 
   @XmlJavaTypeAdapter(MyMapAdapter.class)
   Map<Integer, String> map =
    new HashMap<Integer, String>();
 
   public Map getMap() {
      return map;
   }
 
   public void setMap(Map map) {
      this.map = map;
   }
 
}

In upcoming posts I'll describe extensions in the EclipseLink JAXB (MOXy) implementation that reduce the dependency on XmlAdapter.

Further Reading

If you enjoyed this post, then you may also be interested in:
  • JAXB and Immutable Objects
    In this post an XmlAdapter is used map a domain object with a mult-argument constructor and fields marked final.  This example also demonstrates how the @XmlJavaTypeAdapter annotation can be used at the type level.
  • @XmlTransformation - Going Beyond XmlAdapter
    In this post an EclipseLink MOXy extension (@XmlTransformation) is used instead of @XmlAdapter to provide a little more flexibility for an interesting use case.

No comments:

Post a Comment