It looks like application development is quickly moving away from servlets, and towards javascript. After prototyping a pure javascript web application using web services I realized that servlets are an antiquated way of designing applications. This blog entry describes an approach I used that worked well for me.
Overview
Here's a diagram from my slide deck that describes what I aimed to accomplish
Although I used SOAP web services, the design works equally well with REST services. I did find a few frameworks like DWR, but these didn't meet my needs since I work in a corporate environment that prefers to not use open source code. Besides, I already had a rich set of in house web services available that I wanted to leverage.
Creating Javascript Objects from XML
Since most of code I typically work with is in java, I've been spoiled with all kinds of frameworks and annotations that render things like web services really easy to deal with. Working with javascript means that I need to find similar tools to facilitate unpacking XML into javascript objects much like JAX-WS does for java. I decided to roll my own using dojo class framework and the FastCode eclipse code generation plugin.
The idea is to follow what JAXB does for java in a javascript environment, namely that given some XML I want to have something parse it and hand me a collection of javascript objects - and vice versa from objects to XML. I accomplished this using the FastCode plugin.
Generating Javascript Code from Java Beans
I started by coding a dojo class that contained the functions necessary to parse XML to JS and back to XML, then wrapped a FastCode template around it:
<template type="Dojo declare class from java bean"> <description>Create a dojo class from a java bean.</description> <variation></variation> <class-pattern></class-pattern> <getter-setter>getter-setter</getter-setter> <allowed-file-extensions>js</allowed-file-extensions> <number-required-classes>1</number-required-classes> <allow-multiple-variation>false</allow-multiple-variation> <template-body> <![CDATA[ dojo.provide("${full_class_name}"); dojo.require("com.dsixe._BeanBase"); \n dojo.declare("${full_class_name}", com.dsixe._BeanBase, { \n #foreach ($field in ${fields}) _${field.name} : null, /* ${field.type} */ #end \n /* Copy/paste to save some typing: new ${full_class_name}({ #foreach ($field in ${fields}) \t ${field.name}: null, #end }); */ constructor : function(args) { #foreach ($field in ${fields}) this.setValue(args, "${field.name}"); #end }, \n /* Useful for creating instance after service call */ createFromXML : function(xmlData, elementName) { var list = []; \n if (elementName == undefined){ elementName = "${class_name}"; this is probably the wrong element name } \n var _list = dojo.query(elementName, xmlData); for (var x=0; x < _list.length; x++){ var inst = new ${full_class_name}(); var item = _list[x]; #foreach ($field in ${fields}) #if (${field.type.endsWith("[]")}) \n // Child element contains XML var o_${field.name} = new QUALIFY_YOUR_PACKAGE_NAME_HERE.${field.type}(); fix this line var i_${field.name} = o_${field.name}.createFromXML(xmlData, "${field.name}"); inst.${field.setter}(i_${field.name}); \n #else inst.${field.setter}(this._getValueXML(item, "${field.name}")); #end #end list.push(inst); } return list; }, \n /* Useful for updates/inserts on large objects */ createWire : function(elementName){ var wire = new dojox.wire.ml.XmlElement(elementName); #foreach ($field in ${fields}) #if (${field.type.endsWith("[]")}) \n // Child element contains XML var ${field.name}Wire = []; for (var x=0; x < this._${field.name}.length; x++){ var item = this._${field.name}[x]; ${field.name}Wire.push(item.createWire("${field.name}")); } wire.setPropertyValue("${field.name}", ${field.name}Wire); \n #else wire.setPropertyValue("${field.name}", this._${field.name}); #end #end return wire; }, \n #foreach ($field in ${fields}) ${field.getter} : function (){ return this._${field.name}; }, ${field.setter} : function (arg){ this._${field.name} = arg; }, \n #end }); ]]> </template-body> </template>
The java class that I'm using for this blog:
package com.dsixe.blog; public class ContactBean { private String name; private String phone; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } }
I invoke the plugin using these steps - select template:
Pick java fields:
And voila - a nice dojo class that is very nearly complete:
dojo.provide("com.dsixe.blog.ContactBean"); dojo.require("com.dsixe._BeanBase"); dojo.declare("com.dsixe.blog.ContactBean", com.dsixe._BeanBase, { _name : null, /* String */ _phone : null, /* String */ /* Copy/paste to save some typing: new com.dsixe.blog.ContactBean({ name: null, phone: null, }); */ constructor : function(args) { this.setValue(args, "name"); this.setValue(args, "phone"); }, /* Useful for creating instance after service call */ createFromXML : function(xmlData, elementName) { var list = []; if (elementName == undefined) { elementName = "ContactBean"; // !! this is probably the wrong element name } var _list = dojo.query(elementName, xmlData); for ( var x = 0; x < _list.length; x++) { var inst = new com.dsixe.blog.ContactBean(); var item = _list[x]; inst.setName(this._getValueXML(item, "name")); inst.setPhone(this._getValueXML(item, "phone")); list.push(inst); } return list; }, /* Useful for updates/inserts on large objects */ createWire : function(elementName) { var wire = new dojox.wire.ml.XmlElement(elementName); wire.setPropertyValue("name", this._name); wire.setPropertyValue("phone", this._phone); return wire; }, getName : function() { return this._name; }, setName : function(arg) { this._name = arg; }, getPhone : function() { return this._phone; }, setPhone : function(arg) { this._phone = arg; } });
Picking Apart the Generated Dojo Class
Let's take a closer look at the code which is almost ready to use, but requires a few tweaks.
The template adds a comment providing an easy way to copy/paste code for a constructor. This seems trivial when there are only two fields, but it can get tiresome when the class contains a dozen or more.
/* Copy/paste to save some typing: new com.dsixe.blog.ContactBean({ name: null, phone: null, }); */The constructor delegates parsing of the fields to the superclass _BeanBase. This allows invoking with a subset of parameters if needed.
constructor : function(args) { this.setValue(args, "name"); this.setValue(args, "phone"); },Create from XML is pretty obvious, it provides a way to convert XML to a collection of javascript objects. This code needs to be reviewed to ensure that the element names match the values returned by the service.
createFromXML : function(xmlData, elementName) { var list = []; if (elementName == undefined) { elementName = "ContactBean"; // !! this is probably the wrong element name } var _list = dojo.query(elementName, xmlData); for ( var x = 0; x < _list.length; x++) { var inst = new com.dsixe.blog.ContactBean(); var item = _list[x]; inst.setName(this._getValueXML(item, "name")); inst.setPhone(this._getValueXML(item, "phone")); list.push(inst); } return list; },Create wire will convert a javascript class instance into a dojox.wire.ml.XmlElement object which is needed by the ibm_soap dojo library. If I was using a REST service then I guess I'd have to change this code or add another function to the template.
createWire : function(elementName) { var wire = new dojox.wire.ml.XmlElement(elementName); wire.setPropertyValue("name", this._name); wire.setPropertyValue("phone", this._phone); return wire; },The remainder of the code is standard gettter/setters for the fields. I tried my best to make the code as formal as possible without any kind of javascript magic for a few reasons:
- javascript can get real messy real fast
- this code is intended for use by java developers who aren't javascript experts
Using the Generated Code
Now that I don't have to get my hands dirty with the XML, I can write some nice clean code against dojo classes. In a nutshell, here's how I invoked a web service operation using ibm_soap:
wsdlParser = new ibm_soap.util.WsdlParser(); wsdlParser.parse("contacts.wsdl"); // Create reference to service and set the URL myService = new ibm_soap.rpc.SoapService(wsdlParser.smdObj); myService.serviceUrl = "/axis2/services/ContactService.ContactInfoPort/"; var params = new dojox.wire.ml.XmlElement("getAllContacts"); var deferred = myService.getAllContacts(params); deferred.addCallback(function(xmlData) { var cb = new com.dsixe.blog.ContactBean(); var contacts = cb.createFromXML(xmlData, "contact"); // do something useful with the data }In practice, the code is much shorter if I parse the wsdl into javascript upfront. In addition, the service/url is really only defined once in the application. This leaves me with creating the dojox.wire.ml.XmlElement and invoking the service operation.
Nice!
Attachments:
DsixEBlog_WS.war
References:
Fast Code eclipse plugin http://fast-code.sourceforge.net/
0 comments:
Post a Comment
Are you about to post a generic comment that has nothing to do with this post? Something like "Hey thanks for this very valuable information, BTW here's my website". If so, it will be marked as spam and deleted within 24 hours.
Note: Only a member of this blog may post a comment.