So, What does Groovy Offer?
Groovy has a couple of neat features that can add value to endpoint development:- GPath: GPath is an expression language integrated in Groovy and used to access parts of structured data easily . GPath allows to navigate through XML using the dot notation. So a.b.c means <c> inside <b> inside <a>. To access an attribute, @ is used: a.@x means attribute x of element <a>. Writing an expression for a nonexistent path is possible and does not throw an exception. This flexibility allows for simple duck typing. Of course GPath has other possibilities. We will use GPath to extract information from the request.
- Builders: special Groovy syntax used to construct arbitrary tree structures. Calling any method on a builder produces an element with same name. The value as well as attributes are specified as parameters of the call. We will use DOMBuilder and NamespaceBuilder to produce the XML of the response.
- Spring integration: Groovy has an excellent integration with Spring. Spring 2 "refreshable beans" feature allows to refresh a bean definition written with Groovy without reloading the whole context. Thus, it is possible to update endpoints while our application is online.
Groovy Endpoints
Our Groovy endpoints will extend a base abstract class: AbstractGroovyEndpoint. AbstractGroovyEndpoint will transform the request's payload into a GPathResult which is the Groovy object that supports GPath navigation. Concrete endpoints will implement the abstract Source invokeInternal(GPathResult request) in order to access the request.Before going into the integration details, lets take an example and see how our Groovy endpoints will look like.
An Example
The following XML represents our request. It will be used to query a service about a list of items. Each item is represented by an <item> element. Its reference can be found either in a ref attribute or a nested ref element:<c:queryItemsRequest xmlns:c="http://cafebabe"> <item ref="cat"/> <item ref="dog"/> <item ref="bird"/> <item> <ref>dog</ref> </item> </c:queryItemsRequest>The response looks like the following:
<c:queryItemResponse xmlns:c="http://cafebabe"> <item ref="cat">A cat!</item> <item ref="dog">A dog!</item> <item ref="bird">No description found for bird</item> <item ref="dog">A dog!</item> </c:queryItemResponse>The endpoint extracts the references of the items to query using GPath. It looks for each reference first in a nested <ref> element and then in a ref attribute. After that the service is invoked. Finally, the response is built using Groovy builders and a javax.xml.transform.Result is returned as specified by Spring-WS:
class QueryItemEndpoint extends AbstractGroovyEndpoint { QueryService queryService protected Source invokeInternal(GPathResult request){ def items = [] for(item in request.item){ Item i = new Item() i.ref = item.@ref == "" ? item.ref : item.@ref items.add(i) } items = queryService.query(items) def builder = DOMBuilder.newInstance() def xsd = NamespaceBuilder.newInstance(builder, 'http://cafebabe', 'c') def response = xsd.queryItemResponse(){ for (i in items){ item(ref: i.ref, i.desc) } } return new DOMSource(response) }Finally, the bean definition in Spring:
<lang:groovy id="queryItemEndpoint" script-source="/groovy/QueryItemEndpoint.groovy" refresh-check-delay="1000"> <lang:property name="queryService" ref="queryService" /> </lang:groovy>Notice the refresh-check-delay attribute that tells Spring that this is a refreshable bean. Spring will check whether the delay has expired and refresh the Groovy script if necessary.
Integration with Spring-WS
Spring WS relies on Source/Result interfaces from the javax.xml.transform to abstract XML handling. Groovy defines an XmlSlurper class that parses XML using SAX and produces a corresponding GPathResult object. XmlSlurper is a stateful object that implements org.xml.sax.ContentHandler. The trick is to create a javax.xml.transform.sax.SAXResult with an instance of XmlSlurper as a content handler and to transform the Source of the request using it:public abstract class AbstractGroovyEndpoint extends TransformerObjectSupport implements PayloadEndpoint { public final Source invoke(Source request) throws Exception { XmlSlurper slurper = new XmlSlurper(); Transformer transformer = createTransformer(); SAXResult result = new SAXResult(slurper); transformer.transform(request, result); GPathResult document = slurper.getDocument(); return invokeInternal(document); } protected abstract Source invokeInternal(GPathResult request); }
Remarks
Groovy endpoints I have shown are probably best suitable for services that need to be flexible in the way they deal with clients' requests. GPath is intuitive and easy to write and modify. On the other hand, it is not as powerful as XPath. Also all the collection mapping and data type conversions have to be taken care of "by hand".The source code can be downloaded here.