The basics of a Gradient service
Description: This page explains the how to create an XMPP/Gradient service.
When extending XMPPService, you are given a bare minimum needed to create a service: lifecycle methods, initialisation parameters, and an event handler. A simple example can be found in DummyXMPPService. Note that the init and destroy methods are optional.
While this is a nice start, it doesn't go very far in terms of helping to create Gradient applications. Some basic functionality is missing:
- document request processing
- session management
- incoming & outgoing stanza routing
By happy coincidence, these are exactly the features provided by BaseGradientService, an extension of XMPPService specifically meant for creating Gradient applications.
BaseGradientService document request processing
BaseGradientService declares processPacket and implements a simple server-side stanza routing framework. This framework intercepts document request IQs, deconstructs them and then calls the abstract method processDocumentRequest(String remoteJID, String path, Map parameters).
This method has one Element as a return value, which is the document response IQ child element. An empty response with a new thread ID can be created using XMPPUtils.newDocResponseElement. The remoteJID parameter is the requesting XMPP entity JID, and the path is the request path. The parameter Map contains a keyed list of parameters, each of which may be either a String or an Element.
The response element should have either an src attribute specifying another URL from which to load the document, or have the document root element appended as the child element of the response.
SimpleGradientService provides a good example. The init method loads a document element into memory:
private static final String INDEX_PATH = "/svg/index.svg"
private Element indexSvg;
public void init() {
//loads in the root element of an SVG
//document stored in the web application.
indexSvgElement = XMLUtils.loadElement(sc.getResourceAsStream(INDEX_PATH));
}
This implementation of processDocumentRequest ignores any given parameters and returns this document regardless:
public Element processDocumentRequest(final String remoteJID, final String path, final Map parameters) {
//create the response
final Element response = XMPPUtils.newDocResponseElement();
//add the document
response.appendChild(indexSvgElement);
return response;
}
Note: In this code, both the response and indexSvg elements were created using the XMLUtils and XMPPUtils utility classes. All XML returned by the utility classes have XMLUtils.FACTORY_DOCUMENT as the owner document, so in this case there's no need to use importNode.
In this case, we are returning the document /svg/index.svg via XMPP. If we decide to be nice to the XMPP server, then we can send a redirect instead:
private static final String WEBSITE = "http://ex-337.net/xmppservices";
public Element processDocumentRequest(final String remoteJID, final String path, final Map parameters) {
//create the response
final Element response = XMPPUtils.newDocResponseElement();
//sends a redirect instead of passing
//the document through the XMPP network.
response.setAttributeNS(XMLUtils.GRADIENT_NAMESPACE, "src", WEBSITE+INDEX_PATH);
return response;
}
In this case, WEBSITE+INDEX_PATH is evaluated to http://ex-337.net/xmppservices/svg/index.svg at compile time.
BaseGradientService session management
The last thing we need to do is override the newSession method. By default, this method returns an instance of GradientSession. GradientSession provides thread tracking, and has its own lifecycle and helper methods. GradientSession is not an abstract class, but if no methods are overridden then you have session tracking & helper methods but no per-session event trapping etc.
IQ stanzas sent from a remote JID are passed by BaseGradientService to the appropriate session, where they are handled by the processIQ method of that session. By default, a "501 not implemented" error message is returned.
In this case, the GradientSession instance for SimpleGradientService is small enough that we can put the implementation in an anonymous inner class:
protected GradientSession newSession() {
return new GradientSession() {
public void init() {
log(remoteJID+" session initialised");
}
public void destroy() {
log(remoteJID+" session destroyed");
}
};
}
GradientSession lifecycle
The newSession method is called from within BaseGradientService.processDocRequestIQ when a document request is received from a remote JID for which no session exists. The several protected fields remoteJID, xmppConnection and service are all set after the GradientSession has been instantiated.
The init method is then called. By default this method is empty, but code overriding this method can rely on the non-null presence of the variables remoteJID (best accessed via getRemoteJID), xmppConnection and service (a reference to the owner BaseGradientService instance).
The document request that created this session is then processed. Document requests create new "threads". Threads are added to the session using addThread, which in turn calls the threadAdded method.
A thread remains in existence until the server is informed that the client has navigated away from the document with that thread. Once this happens, subsequent stanzas addressed to that client thread will return an error that is caught by BaseGradientService, which calls removeThread, which in turn calls threadRemoved.
Trapping thread events should be done using threadAdded and threadRemoved. The two methods called by the BaseGradientService are final to guarantee that a record of the thread is stored and removed correctly within each session.
Important: the threadRemoved event is also fired for each thread when the client disconnects from the network, therefore, any code in threadRemoved must not assume the presence of the remote XMPP entity for that session.
A GradientSession remains in existence for a remote JID until an "unavailable" or "error" presence stanza is received from that JID. At this point, the session removed from the internal Map used to route incoming stanzas. Secondly, closeThreads is called. This method fires the theadRemoved event for every active thread. Finally, the destroy method is called.
At this point, any subsequent document requests will create a new session.
Stanza routing
BaseXMPPService relies on packet properties to ensure that the correct data is handled by the correct object. The decision tree is fairly simple, but is easier to explain using a flowchart than nested lists.
- BaseGradientService stanza routing:
- An image - JPEG - (97 Kilobytes)
- Vector graphics - SVG - (33 Kilobytes)
- Original yEd file - (3 Kilobytes)
Highlighted boxes within the flowchart classify actions:
- Blue:
- session methods
- Green:
- errors
- Moccasin:
- lifecycle events
To view the SVG version you'll need either an IE browser plugin from Adobe or Corel, the Croczilla version of Mozilla (not guaranteed to work), Batik Squiggle or the Gradient client, which is based on Squiggle.
The sendStanza method
Since I expect that a lot of XML will be constructed and returned, I've added a shortcut method that allows elements to be sent to clients, with a lot of legwork done for you. The method, sendStanza(String path, GradientSession session, String threadID, List elements), tests the various arguments for null and works out what type of stanza to send by itself.
This lets you avoid composing your own packets every time you want to send data out to Gradient client(s), and will save you from having to maintain extra code. However, there are some combinations of null- and non-null arguments that don't make sense and therefore causes the method to throw an IllegalArgumentException:
- The list of elements cannot be null or empty.
- If
threadIDis not null, then the sendStanza assumes that you're attempting to target a specific document loaded by one client. Therefore,sessionmust not be null.
The decision tree for choosing what type stanza and which extensions to use is provided below:

sendStanza decision tree for stanza type & extension selection.
- The decision tree flowchart is available as:
- An image - JPEG - (x.x Kilobytes)
- Vector graphics - SVG - (x.x Kilobytes)
- Original yEd file - (x.x Kilobytes)
More examples
Another example that is similar in structure to SimpleGradientService is DemoGradientService. This is the code behind the example Gradient services available online. The source code and files are included in the source code.
© 2006. Some rights reserved. Author: Ian Sollars.