Building an Instant Messaging Application Using Jabber/XMPP

This article will describe our experiences with developing a Java-based instant messenger application using Jabber/XMPP (Extensible Messaging and Presence Protocol) - a free, open and public protocol and technology for instant messaging. According to the Jabber Software Foundation, "Under the hood, Jabber is a set of streaming XML protocols and technologies that enable any two entities on the Internet to exchange messages, presence, and other structured information in close to real-time."

Google Talk uses the standard Jabber/XMPP protocol for authenticating, presence, and messaging. But using Jabber goes beyond instant messaging to almost real-time server-to-server communication.

This article will describe the Jabber/XMPP protocol for messaging, the Jabber/XMPP client program based on JFace and Eclipse, and the Jabber/XMPP Java server. These will be illustrated through Open Source Smack and the Wildfire Server 2.4.4. It will offer examples of a login-class plug-in for custom authentication, a plug-in and server extension for custom messages, and a server-side extension for database interactions. Each example will contain the XML messages, client-side code, and server-side code.

What's Different About Jabber?
Compared to traditional IM programs, Jabber:

  1. Is an XML-based messaging protocol that is open and extensible
  2. It lets you build custom client IM applications. We'll talk about Smack, a project based on JFace and Eclipse that provides tools for building custom client applications
  3. It lets you extend the functionality of the server to process custom messages by writing server-side plug-ins. We'll describe the plug-in architecture of the Wildfire Server
What Are the Basic Features of Instant Messaging?
Any instant messaging system will have these basic features: It connects to server, registers new users, logs in, gets presence information, exchanges messages, and does custom interactions (VoIP, Web conferencing, etc.). Below each of these IM features are described using XMPP:

  • Connect to Server - This is done through a XML stream header exchange. The client sends:

    <stream to='192.168.0.12:5222' xmlns='jabber:client'/>

    The client receives:

    <stream id='xxxx' from:'192.168.0.12:5222' xmlns='jabber:
    client'/>

  • Register - The client sends a message to discover the server's requirements for registration:

    <iq type='get' id='reg1' to='192.168.0.12:5222'><query xmlns='jabber:iq:register'/></iq>

    The server responds with a message that lists the fields required for registration:

    <iq type='result' id='reg1'> <query xmlns='jabber:iq:register'>
          <instructions>

    Choose a username and password for use with this service.
    Please also provide your email address.        </instructions>
         <username/>
         <password/>
         <email/>
       </query></iq>

    The client responds by sending the requested information:

    <iq type='set' id='reg2'>
       <query xmlns='jabber:iq:register'>
         <username>a3</username>
         <password>password</password>
         <email>a3@sow.com</email>
       </query>
    </iq>

    On successful registration, the server responds:

    <iq type='result' id='reg2'/>

    Declare Presence and Get Presence Information
    To declare presence information, the client sends:

    <presence xmlns="" id="d6vNV-3" from="mahaveerj@sow/1139352545625"/>

    Note the "to" attribute isn't required in the above message because the client knows which server it's talking to.

    To get presence information, client sends:

    <iq xmlns="" id="d6vNV-2" type="get" from="mahaveerj@sow/1139352545625">
    <query xmlns="jabber:iq:roster"/>
    </iq>

    The server responds with presence data about two people in the roster:

    <iq xmlns="" type="result" id="d6vNV-2" to="mahaveerj@sow/1139352545625">
       <query xmlns="jabber:iq:roster">
          <item jid="jorge@sow" name="Jorge" subscription="both"></item>
          <item jid="bruce@sow" name="Dr. Bruce" subscription="both"></item>
       </query></iq>

    Exchange Messages
    User a1 sends message to user a2:

    <message xmlns="" id="H49LH-12" to="a1@sow" type="chat" from="a2 @sow/1139385809707">
         <body>Good Morning, I am testing chat in Jabber Client.</body>
    </message>

    The elements above form the core of XMPP (Extensible Messaging and Presence Protocol) XML syntax. It also gives you a sense of the flow of XML messages between the client and server to accomplish a task.

    Jabber/XMPP Client and Server
    Since Jabber/XMPP is an open protocol scores of implementations have been created for the client and server, some Open Source and others commercial. There are literally hundreds of Jabber clients available. (See References.)

    In this article we'll use the Smack library (www.jivesoftware.org/smack/) to illustrate client-side functionality. Smack provides an API for implementing the GUI and managing XML data. Managing XML data involves creating XML messages, sending the messages, receiving messages from the server, and processing the incoming messages.

    We'll use the Wildfire Server (www.jivesoftware.org/wildfire/) for illustrating creating server-side plug-ins. For basic IM needs, no server-side programming is required. For other needs Wildfire provides a plug-in architecture for extending the server's functionality. We'll look next at a use case to illustrate how custom messages can be created and processed at both the client and server.

    Use Case 1: Custom Authentication and Custom Queries
    Consider a case in which an IM client and a Web portal are part of a suite of collaboration applications. The Web portal has database-driven MD5-based authentication and the requirement is that IM client use the portal's authentication service. This will allow users to log in to IM and then launch applications on the portal from the IM client without logging into the portal.

    Part A: Custom Authentication
    The Wildfire Server uses the DefaultAuthProvider class to authenticate users and DefaultUserProvider to get user information. These use the database tables Wildfire provides.

    If you want to use a custom authentication method you'll have to change the user and authentication provider in wildfire.xml.

    <provider>
    <user> <className>org.indent.wildfire.user.SOWUserProvider</className> </user>
    <auth><className>org.indent.wildfire.auth.SOWAuthProvider</className> </auth>
    </provider>

    The simple extension defined in Listing 1 uses custom tables and custom encryption methods. The SOWUserProvider class is for user info and SOWAuthProvider is for authentication.

    The two static variables shown define the database authentication strings. And if custom encryption is used then the encryptPassword() method provides the encryption logic.

    Part B: Retrieving a Token from the Database
    In this use case, after authenticating, the client is going to ask the database for an encrypted token that uniquely identifies the client. This token is later used to launch applications in the Web portal without explicitly logging into the portal. The steps are:
    a)  The client generates a custom message to request a token
    b)  The server gets the message and processes the request by getting a token from the database. The server generates a reply message that contains the token and sends it to the requesting client
    c)  The client gets the message and extracts the token

    This process is illustrated in Figure 1.

    Client to Server
    The client generates a <iq> packet that looks like:

    <iq to="serverName" type="get"><query xmlns="jabber:iq:token"/></iq>

    jabber:iq:token is a namespace for our custom query. This message is generated at the client using the following code:

           XMPPConnection connection = session.getConnection();
           ClientID packet = new ClientID();
           connection.sendPacket(packet);

    The Class ClientID is defined in the section "Client-side Processing of Reply Packets" below.

    The Server-Side Plug-In
    The first step is to create a plug-in that will process the custom message with xmlns="jabber:iq:token." The plug-in is called ClientTokenPlugin. It first creates an IQTokenHandler; the handler is then added to the iq router that routes all the incoming iq messages:

    public class ClientTokenPlugin implements Plugin {
        public ClientTokenPlugin() {
           IQHandler iqRDHandler = new IQTokenHandler();
           IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
           iqRouter.addHandler(iqRDHandler);
        }
        public void initializePlugin(PluginManager manager, File pluginDirectory) { }
        public void destroyPlugin() { }
    }

    For more on how to create a plug-in, see www.jivesoftware.org/builds/wildfire/docs/ latest/documentation/plugin-dev-guide.html.

    IQTokenHandler has two key methods:

    a)  getInfo() which returns the type of queries in the iq messages that the token handler is going to process
    b)  handleIQ() which processes the incoming message/packet and creates a reply message/packet

    public class IQTokenHandler extends IQHandler {
       public IQTokenHandler() { super("Client Token Handler"); }
       public IQHandlerInfo getInfo() {
         return new IQHandlerInfo("query","jabber:iq:token"); }

       public IQ handleIQ(IQ packet) {
       IQ replyPacket = IQ.createResultIQ(packet);
       Element m = replyPacket.setChildElement("query", "jabber:iq:token");
    // 2 lines below are specific to our needs; change them to get data according to your needs
       ClientSession session = sessionManager.getSession(packet.getFrom());
       String token = (String)UserTokenList.get(JID.unescapeNode(session.getAddress().getNode().toLowerCase()));
       m.addElement("tokenNum").addText(token);
       return replyPacket;
       }
    }

    The reply message will look like:

    <iq to="clientName">
       <query xmlns="jabber:iq:token">
         <tokenNum>abcdefgh</tokenNum>
       </query></iq>


  • Client-Side Processing of Reply Packets
    At the client end, you'll have to define a class to handles the IQ message. This is defined in a configuration file called smack.
    provider:

       <iqProvider>
         <elementName>query</elementName>
         <namespace>jabber:iq:token</namespace>
         <className>org.jivesoftware.smack.packet.ClientID$Provider</className>
       </iqProvider>

    The iqProvider defines the class and methods for both generating custom iq messages with the namespace jabber:iq:token for sending to server or other clients and for processing incoming iq messages with the namespace jabber:iq:token.

    The first section of the code is for generating an iq packet, as described in the section "Client to Server" above. The provider contains a parseIQ() method to parse the incoming message and extract useful data and the tokenNum in the reply message above and stores them in object c in Listing 2. This ends the lifecycle of the iq packet.

    Use Case 2: The Custom Message
    During collaboration, userA wants to launch a browser with a URL in userB's machine. This will be implemented as a custom <message>. Usually <message> is used to carry chat messages, but in this use case it will be used to carry a custom message (<xsow>) like:

       <message xmlns="" id="WHFl2-7" to="userB@sow" from="userA@sow/1139322562328">
         <xsow xmlns="http://www.indent.org">
           <conf Url="http://my.shareonWeb.com/jetspeed/indent/sowWCLogin.jsp?p=2079810"/>
         </xsow>
       </message>

    The steps are:

    1. At the client SOWExtension, a custom PacketExtension, is implemented to create the message and the provider to parse the incoming custom message
    2. At the receiving client, a packet filter and listener are implemented to trap the custom message and process it.
    This is illustrated in Figure 2.

    Creating a Custom Message at the Client In the use case, the first step for userA to send a custom message to userB is to do the following in the client GUI: userA chooses "Open Document" from a menu or right-clicks on userB in the roster list and chooses "Open Document." The client GUI in our case uses Smack and JFace; "Open Document" triggers a call to the run() method associated with the selection. The run() method creates a message and sends to server, which it then routes to userB.

    public void run() {
       Message iceMessage = new Message();
       SOWExtension icePacket = new SOWExtension();
       String urlToSend = this.appData.aspireRequest + "&p=" + ClientID.token;
       icePacket.setUrl(urlToSend);
       iceMessage.addExtension(icePacket);
       iceMessage.setType(Message.Type.NORMAL);
       for (Iterator i=selection.iterator(); i.hasNext();) {
         Object user = i.next();
         RosterEntry entry = (RosterEntry) user;
         iceMessage.setTo(entry.getUser());
         Session.getInstance().getConnection().sendPacket(iceMessage);
         }
       }

    A standard message/packet is created first; an extension is created on the message using the new SOWExtension() - this is going to carry the custom packet; the custom packet is added to the message using addExtension(icePacket). The for loop then sends the message to each selected user (userB, userC, ...). The sendPacket() method calls toXML() in Listing 3 to create an XML string.

    The SOWExtension class implements PacketExtension; one of the methods of interest is the class is toXML() and the class of interest is the Provider class. The Provider class provides the parse method for incoming custom messages with the extension <xsow>. The provider class will be discussed in the next section.

    Processing the Custom Message at the Client
    Note that the server doesn't do any operation on the message other than route it to userB. userB gets the message first by parsing it using the parseExtension() in Listing 3, and then passing the message through a filter described below. filterExt below is a filter that looks for packets with the extension "xsow." When the filter recognizes such a message, a listener then processes the message. The processPacket() method below first extracts the extension in the custom message by calling message.getExtension(). getExtension() returns an SOWExtension object defined in Listing 3. Since the custom message above has already been parsed, this object contains all the relevant information contained in the packet extension. sowExt.getURL() returns the desired value. The last two lines in listener below correspond to the desired action, in this case, opening a browse with a URL that was contained in the extension of the custom message above.

    PacketListener listener = new PacketListener() {
       public void processPacket(Packet packet) {
         final Message message = (Message) packet;
         SOWExtension sowExt = (SOWExtension) message.getExtension(
         SOWExtension.elementname, SOWExtension.namespace);
         String url = sowExt.getUrl() + "&uname=" +
         Session.getInstance().getConnectionDetails().getUserId();
         Program.launch(url);
       }
    };
    PacketExtensionFilter filterExt = new PacketExtensionFilter("xsow", "http://www.indent.org"); connection.addPacketListener(listener, filterExt);

    The message extension and the SOWExtension's Provider are defined in smack.provider.

       <extensionProvider>
         <elementName>xsow</elementName>
         <namespace>http://www.indent.org</namespace>
         <className>org.jivesoftware.smackx.packet.SOWExtension$Provider</className>
       </extensionProvider>

    Note: iqProvider to handle custom IQ messages was defined in reply packets code above. extensionProvider to handle custom message extension is defined in the code immediately above.

    Summary
    There are several Open Source and commercial implementations of XMPP server and client to choose from that provide basic chat, presence, and roster functionality. Here we discussed how to extend the functionality of your XMPP-based IM applications through two mechanisms: defining custom queries in the <iq> element and defining extensions in the <message> element. Client-side extensions and server-side plug-ins are described to accomplish the custom functionality.

    Some of the specific uses of the <iq> extension will be to create rich IM applications that interact with a backend application server and/or database; examples of uses of the <message> extension include doing things like opening browsers and sending files from one user's client IM application to another.

    References

    Acknowledgements
    We would like to acknowledge partial funding from NIMH of NIH, and the help of Gaurav Mantro for coding some pieces of the client application and thank Ryan Graham, Gaston Dombiak, and a few others for answering questions we posted on www.jivesoftware.org's discussion forum for developers.
    © 2008 SYS-CON Media