Friday, August 9, 2013

XmlAdapter - JAXB's Secret Ninja.

The XmlAdapter mechanism in JAXB ensures that there is no such thing as an unmappable class. 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 XmlAdapte
Recently i ran into a problem where i need to Store/Retrieve data from MongoDB using ApacheCXF and Spring-Data for MongoDB.

Storing data was easy i had no complaints when using Map's. As shown below.



@XmlRootElement(name = "article")
 public class Article implements Serializable{
     
private static final long serialVersionUID = 1L;
private Map<String,Map<String,String>> map = new HashMap<String,Map<String,String>>();
 
    public Article(){}
    public Article(Map<String,Map<String,String>> m){
    this.map = m;
    }
  public Map<String,Map<String,String>> getMap() {
return map;
}
public void setMap(Map<String,Map<String,String>>  map) {
this.map = map;
}
}

Above class was good enough for Create Operation, however when i was trying to retrieve data it was throwing exception that unable to convert Map to XML , JAXB exception all over.

that's when i decided to extend XMLAdapter.

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 ,that contained a list of objects with two properties: key and value . Below are the classes that i implemented to achieve this.


public class MapType {
public List element = new ArrayList();

public class MapEntryType {
@XmlAttribute 
public String name;
@XmlElement
public List<LinkCountMapType> attribute = new ArrayList<LinkCountMapType>();

public class LinkCountMapType {
@XmlAttribute
public String name;
@XmlValue 
public String count;
}  


3. Create an XmlAdapter to Convert Between Unmappable and Mappable Objects
The XmlAdapter 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.




public final class MapAdapter extends XmlAdapter<MapType, Map<String, Map<String, String>>> {

@Override
public Map<String, Map<String, String>> unmarshal(MapType v) throws Exception {
    Map<String, Map<String, String>> mainMap = new HashMap<String, Map<String, String>>();

    List<MapEntryType> myMapEntryTypes = v.element;
    for (MapEntryType myMapEntryType : myMapEntryTypes) {
        Map<String, String> linkCountMap = new HashMap<String, String>();
        for (LinkCountMapType myLinkCountMapType : myMapEntryType.attribute) {
            linkCountMap.put(myLinkCountMapType.name, myLinkCountMapType.count);
        }
        mainMap.put(myMapEntryType.name, linkCountMap);
    }  
    return mainMap;
}

@Override
public MapType marshal(Map<String, Map<String, String>> v) throws Exception {
    MapType myMapType = new MapType();

    List<MapEntryType> entry = new ArrayList<MapEntryType>();

    for (String name : v.keySet()) {
        MapEntryType myMapEntryType = new MapEntryType();
        Map<String, String> linkCountMap = v.get(name);
        List<LinkCountMapType> linkCountList = new ArrayList<LinkCountMapType>();
        for (String link : linkCountMap.keySet()) {
            LinkCountMapType myLinkCountMapType = new LinkCountMapType();
            String count = linkCountMap.get(link);
            myLinkCountMapType.count = count;
            myLinkCountMapType.name = link;
            linkCountList.add(myLinkCountMapType);
        }
        myMapEntryType.name = name;
        myMapEntryType.attribute = linkCountList;
        entry.add(myMapEntryType);
    }
    myMapType.element = entry;
    return myMapType;
}
}



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 Article class. Now during marshal/unmarshal operations the instance of Map is treated as an instance of My MapType.



@XmlRootElement(name = "article")
@XmlAccessorType(XmlAccessType.FIELD)
 public class Article implements Serializable{
     
private static final long serialVersionUID = 1L;
    @XmlJavaTypeAdapter(MapAdapter.class)
private Map<String,Map<String,String>> map = new HashMap<String,Map<String,String>>();
 
    public Article(){}
    public Article(Map<String,Map<String,String>> m){
    this.map = m;
    }
  public Map<String,Map<String,String>> getMap() {
return map;
}
public void setMap(Map<String,Map<String,String>>  map) {
this.map = map;
}
}

Create ElasticSearch cluster on single machine

I wanted to figure out how to create a multi-node ElasticSearch cluster on single machine. So i followed these instructions First i did...