Component Developer Guide

Version 2

    Introduction

    XMPP Components enhance the functionality of an XMPP domain. They receive all stanzas that are addressed to a particular subdomain of the XMPP domain. Two types of components are identified: "internal components" run within the server software and typically have direct access to the server software API. "External components" on the other hand run outside of the server software and connect to the XMPP domain over a network protocol.

     

    For developers of components, Tinder offers the org.xmpp.component.Component interface which can be used to implement an XMPP component. It has two typical usages: used within the context of an Openfire plugin, it can be used to implement an internal component. An external component can be implemented using the Whack library that, internally, uses the same Component interface.

     

    A lot of the work that goes into writing a Component interface is repetitive work. Tasks like stanza handling, Service Discovery request processing and error handling are often duplicated between implementations in the form of boilerplate code. Some of the required functionality, such as making sure that every IQ request is being responded too, is easily overlooked, causing the component to break the XMPP specification. Problems like these are easily overcome by using the abstract implementation of the Component interface which was introduced in version 1.2 of Tinder.

    AbstractComponent features

    The org.xmpp.component.AbstractComponent implementation allows you to develop internal as well as external components (for example, by using it in combination with Whack). It offers a number of features, including:

    • it makes sure that every IQ request it gets sent is responded to. The XMPP specifications state that every IQ stanza of type get or set must be responded to with an IQ stanza of type error or result. The implementation of AbstractComponent requires developers to process IQ request stanzas in specific methods of which the result is verified to be a valid response to the request. If this is not the case, an appropiate error stanza is returned to the originating XMPP entity. Additionally, AbstractComponent makes sure that appropiate error responses are sent in extreme circumstances, such as stanzas being processed during the shutdown of a component or situations where the component is flooded with work.
    • it makes use of the producer/consumer pattern. Component implementations are often the Openfires Achilles' heel. Openfire uses a limited pool of worker threads for a variety of tasks. These threads are also used to call the processPacket() method of Component. This allows for synchronous operation, but adds to the risc of Openfire running out of worker threads. If that happens, the entire XMPP domain usually suffers from dramatic loss of service. Each AbstractComponent instance uses a configurable but dedicated thread pool (which is fronted by a queue) to process stanzas. This way, AbstractComponent is guaranteed to release the thread that delivers the stanza to the component, immediately after the stanza is queued. Additionally, if your AbstractComponent does run out of resources, the problem usually is contained within the component without affecting the rest of the domain.
    • it implements default answers to Service Discovery, Last Activity and XMPP ping requests. All of these XMPP Extension Protocol implementations typically use a lot of boilerplate code. AbstractComponent takes away the need for this boilerplate code and provides easy hooks to extend the functionality that is exposed.
    • it allows a component to serve users of the local domain only. An often overlooked fact is that typical Component implementations serve all XMPP users that connect to it. This includes both users of the local domain, as well as users that connect through federation. Naive implementations assume that a user originates from the local domain, taking into account only the JID node (note that this could lead to serious problems, if a user from another domain, matching the username of a local user attempts to access functionality of such a component). The AbstractComponent implementation offera a configurable switch, that allows one to define whether or not to allow users of other domains to make use of the component. If these users are not allowed, appropiate errors are returned to stanzas sent by these users.

    Working with AbstractComponent

    AbstractComponents default functionality can be changed by overriding specific methods that implement that functionality.

    Specifiying the identity of a component

    To create a running implementation of a component by extending the AbstractComponent class you need to define the identity of your component. This is achived by implementing the three abstract methods of AbstractComponent:

    • getName() must return the name of the component, for example: "testcomponent". This name can be used to generate the subdomain address of the component.
    • getDomain() must return the XMPP domain to which this component will connect, for example: "example.org".
    • getDescription() should return a textual description of the component. This description can be used in administrative software.

    Adding functionality

    For most developers, the methods of which the method name begins with handle are of most interest. Such handle methods exist for every type of stanza: every message stanza that is sent to the component is passed to the handleMessage(Message) method. Similarly, every presence stanza is sent to handlePresence(Presence). Four handle methods have been specified for every type of IQ stanza: handleIQGet(IQ), handleIQSet(IQ), handleIQResult(IQ) and handleIQError(IQ).Overriding any of the handlers is optional: a developer needs only to override a method, if he or she wishes the component to process stanzas of the corresponding type.

    An example

    The following code shows how to create a component that will respond to chat messages with a simple, static response.

     

    import org.xmpp.packet.JID;
    import org.xmpp.packet.Message;
    
    public class HelloComponent extends AbstractComponent {
    
        private JID myAddress = null;
    
        @Override
        public String getDescription() {
            return "A component that will respond with a friendly "
                    + "'hello' to every message it receives.";
        }
    
        @Override
        public String getDomain() {
            return "example.org";
        }
    
        @Override
        public String getName() {
            return "hello";
        }
    
        public void initialize(JID jid, ComponentManager componentManager)
                throws ComponentException {
            this.myAddress = jid;
        }
    
        @Override
        protected void handleMessage(Message received) {
            // construct the response
            Message response = new Message();
            response.setFrom(myAddress);
            response.setTo(received.getFrom());
            response.setBody("Hello!");
    
            // send the response using AbstractComponent#send(Packet)
            send(response);
        }
    }
    

     

    The code above shows how you can quickly implement a functional component. Although we wrote just a few lines of code, this component will correctly respond to any XMPP Ping, Service Discovery and other IQ request it recieves (the last category will be responded to with service-unavailable errors).

    Tuning Service Discovery responses

    Say we choose to identify our functionality with a specific namespace, "example:hello". Our component should add this namespace to the list of features that are exposed by Service Discovery.

     

    AbstractComponent allows you to add new features to the response of Service Discovery, by overriding the discoInfoFeatureNamespaces() method. This method returns an array of namespaces that are added to the list of namespaces that identify features that are implemented. This example shows how the "example:hello" namespace is added to the list of features that is included in Service Discovery responses:

     

    @Override
    protected String[] discoInfoFeatureNamespaces() {
        String[] ns = { "example:sayhello" };
        return ns;
    }
    

     

    Modifying the Threadpool

    Every AbstractComponent instance uses threads from a dedicated threadpool. By default, up to 17 threads are used and 1000 stanzas can be queued, waiting for threads to become available for processing.

     

    As our Hello component will be very fast in processing requests, we can safely tune down the number of threads used. To do this, we make sure the component is instantiated using a constructor that overrides the default number of threads.

     

    public HelloComponent() {
        super(2, 1000, true);
    }
    

     

    I would like to retrofit my component...

    "...but my component does not / can not respond immediately to every IQ request"

     

    AbstractComponent requires a developer to return a valid response to every IQ request stanza that is processed. Null responses are translated into service-unavailable errors. This technique requires implementations to follow the XMPP specification where it states that every IQ request must be responded to.

     

    Not every component can or should respond to a request immediately. If it takes a long time to generate a response, it doesn't make sense to keep the thread from the thread pool waiting. Existing Component implementations won't always be easily rewritten in a way that matches the setup of AbstractComponent. In these situations, it would be helpful to stop AbstractComponent from enforcing responses to every IQ request.

     

    AbstractComponent allows you to switch off the requirement that every IQ that is processed by handleIQSet() or handleIQGet() must be responded to. By setting the boolean flag in the constructor of AbstractComponent to false, AbstractComponent will no longer respond with service-unavailable errors if null is returned by any of those two methods.

     

    public HelloComponent() {
        super(17, 1000, false);
    }
    
    

     

    After doing this, the responsibility of making sure that every IQ request is responded to falls in the hand of the developer.