Developing XMPP Components – Some Tips

In this final part I’ll wrap up this series on writing XMPP components with a few random tips that have I’ve discovered.

Difference between Component and AbstractComponent

In my previous blog, I mention that you can either create XMPP componentby implementing Component or by extending AbstractComponent. So what is the difference between these 2?

Lets look at Component first; it is an interface so you’ll have to do all the work. The Component interface have the usual meta-information (getName(), getDescription()) and lifecycle (initialize(), start(), shutdown()) methods. You will have to implement all these methods. The heart of Component is undoubtedly processPacket(); all stanzas bound for this component are routed to this method. It is the responsibility of the your component to discriminate what type of packet this is (IQ, Message or Presence) and handling them accordingly. The following code snippet shows the general structure of how a typical processPacket() looks like

public void processPacket(org.xmpp.packet.Packet packet) {
   Packet reply = null;
   if (packet instanceof org.xmpp.packet.Message) {
      //Handle message
      reply = …
   } else if (packet instanceof org.xmpp.packet.IQ) {
      //Handle IQ
      reply = …
   } else {
      //Handle Presence
      reply = …
   }
   if (null != reply)
      componentManager.sendPacket(this, reply);
}

AbstractComponent has a much richer functionality. It is both a framework and a runtime/container.

XMPP framework As a framework, it provides nice methods to handle stanzas like handleMessage(), handlePresence() so that you don’t have to worry about figuring out what each stanza is. AbstractComponent also have specialize methods for dealing with disco#info (handleDiscoInfo()), disco#items (handleDiscoItems()), IQ get (handleIQGet()), etc. There are also methods to advertise your component’s services; you do this by overrding  discoInfoFeatureNamespaces(), discoInfoIdentityCategory(), discoInfoIdentityCategoryType() and returning appropriate values. There are also lifecycle methods: preComponentStart(), postComponentStart(), etc.

Runtime environment AbstractComponent provides default implementation for some services like ping and last activity. It also creates a threadpool to dispatch request so that when you are handling a stanza eg. in handleMessage(), so you don’t have to worry about holding on to the message dispatching thread. The default threadpool size is 17.

Most of you will undoubtedly choose AbstractComponent over Component. One of the first and immediate advantage is that with AbstractComponent, you don’t have to worry about trying to figure out what type of message you are getting. All these is done for you. AbstractComponent also automatically handles some of the messages for you like disco#info. All this just makes it really easy for you to develop XMPP components.

There is a great write up for AbstractComponent. See “Component Developer Guide“.

A word about threads: when you use Whack to create XMPP component, Whack ‘engine’ wraps your Component or AbstractComponent in an ExternalComponent. ExternalComponent provides the infrastructure to connect to an XMPP server, does authentication and also routes stanzas between your component (Component.processPacket()) and the XMPP server (ComponentManager.sendPacket()). ExternalComponent also creates a threadpool and routes messages using thread from the pool. So if you are using AbstractComponent, then there is a double thread dispatch, the handling off of the stanza from ExternalComponent thread to AbstractComponent thread. While this is not really a big deal, it is IMHO not a really great way to utilize resources. You can tune the number of threads in AbstractComponent but not in ExternalComponent.

org.xmpp versus org.jivesoftware.smack API

If you have been working with Smack API, one of the things that you will quickly discover is that both Component and AbstractComponent do not use this API. It uses a simpler and more generic API, lets call it org.xmpp, that is based on dom4j. Lets look at their differences by way of an example; if you are using Smack, here is how you would construct a reply to a disco#info

DiscoverInfo.Identity id = new DiscoverInfo.Identity(“automation”, “Convert to uppercase”);
id.setType(“uppercase”);
DiscoverInfo discoInfo = new DiscoverInfo();
discoInfo.addIdentity(id);
discoInfo.addFeature(“urn:xmpp:uppercase”);

The Smack code snippet will produce the following fragment (actually it produces more than that, but I’m just concentrating on the disco#info bit)

<query xmlns=”http://jabber.org/protocol/disco#info”>
   <identity category=”automation” name=”Convert to uppercase” type=”uppercase”/>
   <feature var=”urn:xmpp:uppercase”/>
</query>

The following code snippet shows how to produce a similar fragment using org.xmpp

//Get an instance of Element typically from IQ.createRequest() or similiar methods
Element discoInfo = …
discoInfo.addElement(“identity”)
      .addAttribute(“category”, “automation”)
      .addAttribute(“type”, “uppercase”)
      .addAttribute(“name”, “Convert to uppercase”);
discoInfo.addElement(“feature”)
      .addAttribute(“var”, “urn:xmpp:uppercase”);

As you can see that although org.xmpp is simpler, generic and more lightweight. But you do have to know the XMPP protocol to construct the stanza. If you are are, like me, conversant in Smack API and do not wish to know the ins and outs of the protocol then you may have a problem because Component, AbstractComponent and ComponentManager relies on org.xmpp heavily.

So my solution is to convert org.xmpp objects to Smack objects for manipulation and then convert Smack objects back to org.xmpp objects for dispatching. The following code snippet shows how convert from org.xmpp to Smack

//Component.handleMessage()
public void processPacket(org.xmpp.packet.Packet packet) {
   //First create a parser
   XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
   factory.setNamespaceAware(true);
   XmlPullParser parser = factory.newPullParser();
   parser.setInput(new StringReader(packet.toXML());
   //You MUST call next because Smack helpers assume that the first token
   //is ready to be read
   parser.next();

   //Decide what type of packet this is and call the appropriate helper
   //method from Smack package for parsing
   if (packet instanceof org.xmpp.packet.Message)
      org.jivesoftware.smack.packet.Message message =
            PacketParserUtils.parseMessage(parser);

   else if (packet instanceof org.xmpp.packet.IQ)
            …
}

The hard work is done by PacketParserUtils; determine what type of packet you are dealing with and then call parseMessage(), parsePresence() or parseIQ(). parseIQ() method seems to be missing from Smack 3.1. You will have to get the nightly builds to use it. One word of caution, org.xmpp and Smack API  share some classes with the same names like Message, IQ, Presence, etc. Just be aware which API you are using. One nice side effect of using Smack to parse your packets is that if you have installed Smack custom providers, PacketParserUtils will be able to pick them up.

After you have manipulated the Smack object, you want to convert it back to org.xmpp object before dispatching it. The following code snippet shows how this is done

//message is org.jivesoftware.smack.packet
Document doc = DocumentHelper.parseText(message.toXML());
org.xmpp.packet.Message toSend = new org.xmpp.packet.Message(doc.getRootElement());

To reverse the process, we get a stringified stanza (toXML()) of the Smack object, and parse that into a dom4j document. Then wrap that in a org.xmpp Message object to be send out. The DocumentHelper class is from dom4j.

I’ve not done any benchmarking or sizing as to the efficiency of this method. Let me know if you have any data on this. Maybe I’ll collect some data in the future.

Publishing component’s disco#info

After the component has successfully authenticated with the server, one of the first stanza that the server will be sending to your component will be a disco#info get. What the server is trying to get is more information about the services that your component is offering. If you fail to response to the disco#info, then the server might not list your component with the server receives a disco#item. This is the behaviour of Openfire.

Lets assume that we are going to return the above disco#info. Here is how you do it if you implementing Component

public void processPacket(Packet packet) {
   if (packet instanceof org.xmpp.packet.IQ) {
      IQ iq = (IQ)packet;
      Element iqElem = iq.getChildElement();
      if (“query”.equals(iqElem.getName()) &&
            “http://jabber.org/protocol/disco#info&#8221;.equals(iqElem.getNamespaceURI())) {
         IQ reply = IQ.createResultIQ(iq);
         Element element = reply.setChildElement(“query”
               ,
http://jabber.org/protocol/disco#info&#8221;);
            //Construct the reply as in previous section
            …
            //Send the reply
            componentManager.sendPacket(this, reply);

      } else // it is something else
         …
   }
}

However if you are extending AbstractComponent, this is how you would handle the server’s disco#info

@Override protected String[] discoInfoFeatureNamespaces() {
   return (new String[]{“urn:xmpp:uppercase”});
}
@Override protected String discoInfoIdentityCategory() {
     return (“automation”);
}
@Override protected String discoInfoIdentityCategoryType() {
   return (“uppercase”);
}

Until next time.

Advertisements
%d bloggers like this: