Server-side XMPP service basics
Description: This page contains an overview of the server-side application framework architecture used to create XMPP and Gradient applications, and how it interacts with the servlet container.
Important: This whole shebang should be built on top of EJB with factories, proxies etc. Version 1 of the framework (ie. this code) and all subsequent point releases will not use EJB. Version 2 will.
Since there is no Java application framework that I know of intended for use with XMPP services, robots, etc. that is license-compatible with Apache code, (although I probably could have looked harder), I wrote something myself using the Smack API. This simple framework is intended for use in writing Gradient services that respond to document requests.
Theoretically it could also be used to write XMPP robots. I'm working on abstracting the Gradient code away from the basic XMPP session management.
The framework as-is allows you to create Gradient/XMPP services whose lifecycle events are dependent on that of a servlet container.
How does it work?
Put simply, the XMPPServiceManagerServlet servlet loads implementations of the XMPPService class and initialises them with a specified connection within the init code, and destroys them when the servlet itself is destroyed. Eventually the servlet will also also provide an admin interface to allow services to be stopped, started etc, and automatically attempt to reconnect disconnected XMPP connections using an exponential backoff scheme.
XMPPServiceManagerServlet was written on top of HelperServlet, a basic helper servlet I use in several projects.
All services are specified & configured using the parameters of this servlet. An example configuration for this servlet is presented piecemeal below and explained. First the normal stuff:
<servlet>
<servlet-name>xmppservices</servlet-name>
<servlet-class>net.ex_337.xmpp.fwork.XMPPServiceManagerServlet</servlet-class>
<init-param>
<param-name> logfile directory </param-name>
<param-value> /usr/local/tomcat/logs </param-value>
</init-param>
So far, so boring. The directory in which the service manager stores log files is specified.
Next up is this monstrosity:
<init-param>
<param-name>Gradient services</param-name>
<param-value>
# this is a line-break separated set of tab-separated value pairs.
# this is a comment.
gradient://gradient-testing:password@jabber.org (tab) net.ex_337.gradient.util.DummyXMPPService
gradient://gradient-examples:password@jabber.org (tab) net.ex_337.xmpp.fwork.examples.DemoXMPPService
</param-value>
</init-param>
Important: Because the passwords used to connect the service JIDs to the XMPP network are stored in plain text within web.xml, that makes web.xml a high value target for intruders.
Any code that pipes out files from the local filesystem, especially within the WEB-INF directory, should be checked for potential weaknesses, but I'm sure you knew that already.
Dear reader, your eyes do not deceive you: in defiance of the laws of nature, Ian pasted a two-column tab-separated value table into a web application deployment descriptor.
As the comment says, this parameter is a series of tab-separated value pairs, each pair being separated by a tab (highlighted above). Lines starting with # are not considered.
The first parameter is a fully-qualified URI, which is a convenient way of storing a usercode, password and domain in one easily-parsable string.
The second parameter is the full name of a class that extends the class XMPPService. It is also the name of the servlet parameter that contains the initialisation parameters for that class:
<init-param>
<param-name>net.ex_337.xmpp.fwork.examples.DemoXMPPService</param-name>
<param-value>
#example application parameters.
broadcast interval (tab) 12000
message interval (tab) 6000
</param-value>
</init-param>
If the XMPPService extension requires no parameters, then a parameter with this name is not mandatory.
Finally, we instruct the whole mess to spring to life when the container starts up:
<load-on-startup>10</load-on-startup>
</servlet>
And we're done.
The lifecycle of an XMPPService implementation
XMPPServices are loaded in the order in which they were specified in the "xmppservices" parameter. Each named class is first loaded into the VM and then instantiated with a call to newInstance. XMPPService itself defines a zero-arg constructor, which you should feel free to override.
If the loading or instantiation of a Gradient service throws any Exceptions, then the Exception is logged, and the servlet moves on, attempting load the next service, if any.
Provided that the service has been successfully instantiated, then any initialisation parameters specified in the relevant servlet parameter will be set using setInitParameter.
Note: there are two helper methods, getIntInitParameter and getListInitParameter, that may make life easier. getListInitParameter assumes that the parameter is a comma-separated list of values.
Following the setting of the initialisation parameters, the connection URI containing the login information is parsed. Any errors, or the absence of a usercode and/or password, causes an error to be logged. The servlet will then continue onto the next service, if present.
XMPPServiceManagerServlet then attempts to (a) create a connection to the host specified in the connection URI, using port 5222 if no port is specified, then (b) login to the XMPP server with the given usercode and password and the resource "gradient".
If an error occurs during this process, the error is logged, and the servlet moves onto the next XMPP service.
Once this has been done, the methods setXMPPConnection and setServletContext are called.
XMPPService.init is then called. Any Exceptions thrown by this method will cause the XMPP connection to be closed, and the servlet to continue to the next application. Otherwise, the XMPPService is now up and running.
Now that the service is wide awake, open is called. This final method adds the service as a PacketListener to the connection, using a PacketFilter that can be set within the init method. If no PacketFilter is set, XMPPUtils.ALL_PACKETS is used, and all stanzas received by that connection (except IQ RESULT and ERROR stanzas) will be forwarded to processPacket.
When the destroy method on the servlet is called (i.e. the servlet container is destroying the servlet), then each loaded XMPP service is closed.
Firstly, the stop method is called. This removes the service as packet listener from the connection, preventing further calls to processPacket(). Then the destroy method is called. Any cleaning up code should go in here. Finally, the close methods is called, which closes the XMPP connection.
Services are closed in the reverse order from which they were loaded.
This explains how an XMPPService is loaded. "Gradient service basics" explains how to create an XMPP service to be loaded.
© 2006. Some rights reserved. Author: Ian Sollars.