Friday, May 13, 2011

XML to POJO Mapper for GWT using a GDATA Example with Google Contacts (ContactEntry).

If your like me I dislike cowboy coding.  You are hacking away at something to get the job done, but the nagging thought at the back of your head is telling you "You kow you shouldn't be doing this".  The best path maybe unavailable because a lack of experience, or perhaps we are unaware of suitable boilerplate-removing framework.  We've all been there time is in the essence, deadlines are pressing and your new TPS Reports Engine needs to be delivered yesterday.  Well I found myself in such a quandry recently and found a tool which will solve a common problem for many GWT developers. So instead of waffling what actually is the problem?

Imagine an application that communicates with Google Contacts.  Server-side you receive a an atom-rss feed representation of a contact in the form of a ContactEntry object.  Google kindly provides you with GData POJOs which are already populated with the information you require.  On the server this is beautiful, Google has done all the hard work and the POJO is populated with data.  If you were using the wonderful Spring MVC you could render the contact information very easily.  However you are a profound, if not magical developer that has the GWT Swiss army knife in their toolkit, and you want to send this POJO to the client.  In the words of Mr. J. Carrey you reach the Alrighty Then brick wall of stoppage.  The ContactEntry POJO is not serializable to the client.  Or is it?

One giant caveat is that I maybe encountering a newbie problem and someone may have created an easy way to make GData objects serializable for sending over GWT-RPC, but at present the only way I have found to do it, is manually, i.e. boilerplatery.  Create my own ContactEntry objects on the server and populate them.  This obviously involves copious amounts of repetition.  It can be done this way, and is the way I have done it in the past, before I knew better, but it isn't efficient. 

Lets recap:  We want a solution to send a GData ContactEntry object to the GWT client that we can use in a POJO, without becoming America's best plumber to create all the additional boilerplate code.

My proposed solution:  Send the XML as a String to the client and use a fancy GWT plugin, Piriti, XPath to auto populate a client side POJO.  Quick, efficient and maintainable.  So how do we do this?

Caveat:  If someone has a better way of doing this please let me know.  I am all ears.  This may not be the best solution and I would love to know how anyone else has tackled this issue.

Step 1: Extract XML
Lets assume you know how to get the ContactEntry from Google using their GData library but now you want to convert that into XML to transport to the GWT Client:
public String getContactEntryXml(ContactEntry entry) {
StringWriter sw = new StringWriter();
String entryXml = "";
try {
XmlWriter xw = new XmlWriter(sw);
entry.generate(xw, contactServiceFactory.getBasicContactsService().getExtensionProfile());
entryXml = sw.toString();
} catch (IOException e) {
e.printStackTrace();
}

return entryXml; // sw.toString();
}
Step 2: Transfer to Client
I am assuming you know how to write GWT applications and communicate back and forther between the server and client.  There are man built in libraries to do this, GWT RPC, Request Factory, and may external third party modules; net.customware.gwt.dispatch, GWTP etc.
Step 3: Create POJO
The key here is reaally the library we are going to use.  After looking at many examples I am using Piriti's library.  It appears to be well used and has good documentation: http://code.google.com/p/piriti/
Piriti (Maori for "bridge") is a JSON and XML mapper for GWT based on annotations and deferred binding. The following code snippets show the basic idea behind Piriti.   
So create a POJO and add these lines to the top of your POJO class:
public class ContactEntrySoho {

public static interface ContactEntrySohoReader extends XmlReader { }
public static final ContactEntrySohoReader XML = GWT.create(ContactEntrySohoReader.class);
These lines are used when mapping the POJO members to their XML nodes.
Step 4 : Map atom:title to an instance member
Let's start easy lets map the atom:title entry of the ContactEntry XML.  First it is probably worth taking a look a the XML:
<atom:title type='text'>Alan UserA1</atom:title>
Now lets look at how we would map this using XPath in the Pojo:
@Path("atom:title") private String title;
For a more in-depth view of XPath look online for various cheat-sheets and tutorials it is very powerful and very useful.
Step 5 : Load the Pojo
All we need to do now is load the POJO with information to do this see the below:
@Override
public ContactEntrySoho parse(String xml) {
try {

Map<String, String> namespaces = new HashMap<String, String>();
namespaces.put("atom", "http://www.w3.org/2005/Atom");
namespaces.put("gContact", "http://schemas.google.com/contact/2008");
namespaces.put("batch", "http://schemas.google.com/gdata/batch");
namespaces.put("gd", "http://schemas.google.com/g/2005");
Document doc = new XmlParser().parse(xml, namespaces);

//Document doc = new XmlParser().parse(xml, NAMESPACES);
ContactEntrySoho sContactEntry = ContactEntrySoho.XML.read(doc);
return sContactEntry;

} catch (Exception e) {
return null;
}
}
The namespaces tell the parser what the tag mean and correspond to the root note elements of the ContactEntry XML:
<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' 

xmlns:gContact='http://schemas.google.com/contact/2008'  

xmlns:batch='http://schemas.google.com/gdata/batch' 

xmlns:gd='http://schemas.google.com/g/2005'>
Remember those additions we added to the POJO we simply call those (XML.read) to parse the xml Document.  Et Voila!  You have your own POJO created with hatever components from the XML you require.
Step 6 : A more complex example please sir.
As you can see I have chosen the easiest node to map and as all articles which only go into the most basic of examples infuriate me, I shan't do the same here.  Let's take a look at an example where we need to map to another POJO.  Take the StructuredPostalAddress component of the ContactEntry class.
The XML in ContactEntry:
<gd:structuredPostalAddress primary='false' rel='http://schemas.google.com/g/2005#home'>
<gd:formattedAddress>6217 Woodlawn Ave N, Seattle, WA. 98103</gd:formattedAddress>
<gd:street>1234 Acme Ave N</gd:street>
<gd:postcode>11111</gd:postcode>
<gd:city>Seattle</gd:city>
<gd:region>WA.</gd:region>
</gd:structuredPostalAddress>
The data in our parent ContactEntrySoho class:
@Path("//gd:structuredPostalAddress") private List<GDStructuredPostalAddress> gdStructuredPostalAddresses;
Wait what is GDStructuredPostalAddress? This is another POJO with the headers defined in Step 3.
public class GDStructuredPostalAddress extends ABaseElement{

public interface GDStructuredPostalAddressXmlReader extends XmlReader<GDStructuredPostalAddress> {}
public static final GDStructuredPostalAddressXmlReader XML = GWT.create(GDStructuredPostalAddressXmlReader.class);

@Path("gd:formattedAddress") private String formattedAddress;
@Path("gd:street") private String street;
@Path("gd:postcode") private String postcode;
@Path("gd:city") private String city;
@Path("gd:region") private String region;
Et Voila! I found when using this that the POJO members sometimes had to be public otherwise they wouldn't populate but this problem was intermittent, so I am unsure whether this is an issue with Piriti's excellent XML->POJO Mapper or my inept code ;).
I would love to hear how other people are doing this as this seems like a good solution but I am sure there are many others.

4 comments:

  1. i actually tried running piriti but when i use name spaces i am getting errors and exceptions while parsing..it runs ok for xml with ni namespace..can you share ur pirii demo project for help?

    ReplyDelete
  2. This turned out to be extremely helpful when serializing the massive ContactEntry class! Thanks! Saved my day.

    ReplyDelete