If you are looking for Jitsi Meet, the WebRTC compatible video conferencing product click here.

previous | TOC | next

Implementing a Protocol

OperationSetPersistentPresence.

Let’s first remind what an operation set is. Different protocols support different functionalities. Operation sets are our way to address this diversity. We could pick and implement any subset of all existing operation sets according to what our protocol or protocol stack supports and what we feel like working on now.

The presence and persistent presence operation sets are the once that follow and update the status of your buddies. They are the ones that would fire PresenceStatusChangeEvent-s when one of your buddies changes its status. Other bundles subscribe to these notifications by adding buddies in the contact list, using the subscribe() methods.

The difference between the persistent and non-persistent presence operation sets is that a persistent presence operation set would store your contact list on the server. In other words once you create a subscription it would remain there even if you restart the application and will not be cancel until you manually remove it (hence the name). Protocol implementing persistent presence are ICQ, MSN, Jabber, Yahoo! Messenger and vitrually all of the most popular IM protocols.

Non persistent presence operation sets, however would require you to re-subscribe to all your contacts every time you start Jitsi. SIP/SIMPLE for example is a protocol that only supports non persistent presence.

Since implementing persistent presence is the same as implementing non-persistent presence but with some extra server stored contact list management, we would only go through a persistent presence operation set implementation in this tutorial.

Before diving into the implementation of the operation set however, we will first implement two other interfaces necessary for it to work - a Contact and a ContactGroup.

Contact-s

A Contact is the immediate result of a subscription. When you call the subscribe() method for a certain contact address, the presence operation set would create a new contact and notify interested parties through a SUBSCRIPTION_CREATED SubscriptionEvent delivered through a SubscriptionListener. A Contact instance contains basic details for the person that it represents, such as a display name, the subscription address (contact identifier) and others. Implementations of a Contact are quite trivial and they often come down to an almost 1:1 encapsulation of the corresponding protocol stack object. And since in Gibberish we don’t have a protocol stack it will be even simpler than that. I will therefore not go through explanations of all the methods as they are quite straightforward and will only explain those that are slightly more obscure.The complete implementation of the gibberish contact is avalable here: ContactGibberishImpl.

One of the concepts that I find kind of hard to grasp in a contact implementation is those that accompany the methods:

/**
 * Determines whether or not this contact is being stored by the server.
 * Non persistent contacts are common in the case of simple, non-persistent
 * presence operation sets. They could however also be seen in persistent
 * presence operation sets when for example we have received an event
 * from someone not on our contact list. Non persistent contacts are
 * volatile even when coming from a persistent presence op. set. They would
 * only exist until the application is closed and will not be there next
 * time it is loaded.
 *
 * @return true if the contact is persistent and false otherwise.
 */

public boolean isPersistent()
{
    return isPersistent;
}

The javadoc kind of says it all. In your experience as an IM user you may have already noticed that when you receive messages from an unknown person, a new contact corresponding to that person would appear in your contact list. Some clients would put this new contact in a special group that is often called something like “Not In Contact List” and would only keep it there until you restart the application. That’s something we do too. When we receive messages in Jitsi coming from people not on our list, we don’t automatically add them there (guess there’s no need to explain the reasons as they are quite obvious). To actually add them to the contact list, a user has to manually move them to one of the other groups. Such contacts, as well as the special “NotInContactList” group, are called non-persistent or volatile as they don’t exist on the server and won’t be in your contact list after you restart.

Another concept that is somewhat tricky to grasp here is that of a contact being resolved or not.

/**
 * Determines whether or not this contact has been resolved against the
 * server. Unresolved contacts are used when initially loading a contact
 * list that has been stored in a local file until the presence operation
 * set has managed to retrieve all the contact list from the server and has
 * properly mapped contacts to their on-line buddies.
 *
 * @return true if the contact has been resolved (mapped against a buddy)
 * and false otherwise.
 */

public boolean isResolved()
{
    return isResolved;
}

Explaining this would require explaining another important service we use in Jitsi - the MetaContactListService. It manages all protocol contact lists so that other bundles such as the user interface would only have to handle a single contact list. It also allows you to merge separate protocol contacts in meta contacts. The meta contact lists needs to store its content locally in order to save the way you have merged separate contacts or any other preferences that you may have specified such as custom display names or user info.

Another reason to store meta contact list content is that this gives us the possibility to show the contact list to the user even if they are not online and this is often very helpful. However, since the meta contact list encapsulates real proto contacts, it has to retrieve references to Contact objects that it could wrap in meta contacts. Furthermore, it has to do have a mechanism of doing so without the protocol being online. That’s where unresolved contacts come in. A presence operation set allows the creation of such contacts even if it is not currently logged in.

Think of this as a buffer mechanism. The protocol provider is accepting creation of unresolved contacts without any limitation. Once logged in, it will retrieve the contact list from the server and compare it to the list of unresolved contacts that have been already created. It would then fire CONTACT_RESOLVED events for those that match and SUBSCRIPTION_REMOVED events for those that haven’t been found and have apparently been removed since the previous run of Jitsi.

ContactGroup-s

ContactGroup-s are even simpler than contacts. They don’t change their status, they don’t have a picture and lots of other complicating details that exist with contacts. The isPersistent() and isResolved notions that we saw in Contact-s however also exist with ContactGroup-s.

The most particular thing about a ContactGroup is of course the fact that it stores contacts. In our Gibberish contact group implementation ( you could have a look at it here: ContactGroupGibberishImpl) we use a vector to store all member contacts. We also use a second vector to store subgroups if they esist. The class also offers methods for retrieving and searching through contacts but I am not going to go through all of them since they are quite trivial and you could study them in detail by looking at ContactGroupGibberishImpl.

The operation set

So here we are, we have an implementation for Contact, another one for ContactGroup and all that’s left now is writing the Gibberish implementation of the persistent presence operation set. Let’s with trivial stuff such as initialization:

public OperationSetPersistentPresenceGibberishImpl(
        ProtocolProviderServiceGibberishImpl        provider)
{
    this.parentProvider = provider;
    contactListRoot = new ContactGroupGibberishImpl("RootGroup", provider);

    //add our unregistration listener
    parentProvider.addRegistrationStateChangeListener(
        new UnregistrationListener());
}

Our constructor registers a reference to the creating provider, and adds a registration listener so that it would notify us if we disconnect. We also create our root gibberish group where we will be adding contacts.

Next we need to define add, remove and fire methods for all the events that this operation set would have to handle. They all look alike so I am only going to paste one of them here - the contact presence status listenr:

/** ... javadocs */
public void addContactPresenceStatusListener(
                    ContactPresenceStatusListener listener)
{
    synchronized(contactPresenceStatusListeners)
    {
        if (!contactPresenceStatusListeners.contains(listener))
            contactPresenceStatusListeners.add(listener);
    }
}

/** ... javadocs */
public void removeContactPresenceStatusListener(
    ContactPresenceStatusListener listener)
{
    synchronized(contactPresenceStatusListeners)
    {
        contactPresenceStatusListeners.remove(listener);
    }
}

/** ... javadocs */
public void fireContactPresenceStatusChangeEvent(ContactGibberishImpl  source,
                                                 ContactGroup parentGroup,
                                                 PresenceStatus oldValue)
{
    ContactPresenceStatusChangeEvent evt
        = new ContactPresenceStatusChangeEvent(source, parentProvider
                    , parentGroup, oldValue, source.getPresenceStatus());

    Iterator listeners = null;
    synchronized(contactPresenceStatusListeners)
    {
        listeners = new ArrayList(contactPresenceStatusListeners).iterator();
    }


    while(listeners.hasNext())
    {
        ContactPresenceStatusListener listener
            = (ContactPresenceStatusListener)listeners.next();

        listener.contactPresenceStatusChanged(evt);
    }
}

The above example shows you how to add listeners and a fire method for ContactPresenceStatusEvent-s - the event’s that are dispatched when contacts in our contact list change their status. We’d have to do pretty much the same for

  • ProviderPresenceStatusChangeEvent-s - those would be delivered if we change our own status
  • ServerStoredGroupChangeEvent-s - notifying listeners of changes in a server stored ContactGroup.
  • SubscriptionEvent-s - that would notify listeners when a Contact has been added or removed from our contact list or moved to another server stored group.

Apart from that, don’t hesitate to look for inspiration in our own OperationSetPersistentPresenceGibberishImpl if you need help finalizing it on your own.

Subscriptions and authorizations.

Handling subscriptions in Gibberish would have been quite straightforward if it weren’t for that feature that we talked about in the beginning. We said that we would like multiple instances (accounts) of a Gibberish ProtocolProviderService to be able to communicate with each other as they would for a real world protocol. In other words, when change our status we’d like other instances of the Gibberish protocol provider service to see that we did so. And to make it look completely realistic we’d also like both providers to request authorizations from each other before actually adding each other to their contact lists.

Before I show you how we’ve implemented this, I’d like to explain how we generally handle authorizations (you know these annoying messages that you see when someone wants to add you to their contact list and when you add contacts to yours). We have an interface that’s called an AuthorizationHandler and that is generally implemented by the user interface. The implementation (pretty much like that of the SecurityAuthority) comes down to simply showing a dialog box to the user when an authorization request arrives and returning an authorization response with the answer given by the user.

So, obviously we’d need to add management for these authorizations in our Gibberish subscribe() method.

public void subscribe(ContactGroup parent, String contactIdentifier) throws
    IllegalArgumentException, IllegalStateException,
    OperationFailedException
{
    ContactGibberishImpl contact = new ContactGibberishImpl(
        contactIdentifier
        , parentProvider);

    if(authorizationHandler != null)
    {
        //we require authorizations in gibberish
        AuthorizationRequest request
            = authorizationHandler.createAuthorizationRequest(contact);


        //and finally pretend that the remote contact has granted us
        //authorization
        AuthorizationResponse response
            = deliverAuthorizationRequest(request, contact);

        authorizationHandler.processAuthorizationResponse(
            response, contact);

        //if the request was not accepted - return the contact.
        if(response.getResponseCode() == AuthorizationResponse.REJECT)
            return;
    }
    ((ContactGroupGibberishImpl)parent).addContact(contact);

    fireSubscriptionEvent(contact,
                          parent,
                          SubscriptionEvent.SUBSCRIPTION_CREATED);
    //if the newly added contact corresponds to another provider - set their
    //status accordingly
    ProtocolProviderServiceGibberishImpl gibProvider
        = findProviderForGibberishUserID(contactIdentifier);
    if(gibProvider != null)
    {
        OperationSetPersistentPresence opSetPresence
            = (OperationSetPersistentPresence)gibProvider.getOperationSet(
                OperationSetPersistentPresence.class);

        changePresenceStatusForContact(
            contact
            , (GibberishStatusEnum)opSetPresence.getPresenceStatus());
    }
    else
    {
        //otherwise - since we are not a real protocol, we set the contact
        //presence status ourselves
        changePresenceStatusForContact(contact, getPresenceStatus());
    }

    //notify presence listeners for the status change.
    fireContactPresenceStatusChangeEvent(contact
                                         , parent
                                         , GibberishStatusEnum.OFFLINE);
}

All we do here is request an authorization through deliverAuthorizationRequest(), create an instance of ContactGibberishImpl and add it to the corresponding group. We also change its status to match our own since that’s how we’d like it to behave. All the fun stuff with the authorizations is actually hidden inside the deliverAuthorizationRequest() method:

private AuthorizationResponse deliverAuthorizationRequest(
            AuthorizationRequest request,
            Contact contact)
{
    String userID = contact.getAddress();

    //if the user id is our own id, then this request is being routed to us
    //from another instance of the gibberish provider.
    if (userID.equals(this.parentProvider.getAccountID().getUserID()))
    {
        //check who is the provider sending the message
        String sourceUserID = contact.getProtocolProvider()
            .getAccountID().getUserID();

        //check whether they are in our contact list
        Contact from = findContactByID(sourceUserID);

        //and if not - add them there as volatile.
        if (from == null)
        {
            from = createVolatileContact(sourceUserID);
        }

        //and now handle the request.
        return authorizationHandler.processAuthorisationRequest(
            request, from);
    }
    else
    {
        //if userID is not our own, try a check whether another provider
        //has that id and if yes - deliver the request to them.
        ProtocolProviderServiceGibberishImpl gibberishProvider
            = this.findProviderForGibberishUserID(userID);
        if (gibberishProvider != null)
        {
            OperationSetPersistentPresenceGibberishImpl opSetPersPresence
                = (OperationSetPersistentPresenceGibberishImpl)
                gibberishProvider.getOperationSet(
                    OperationSetPersistentPresence.class);
            return opSetPersPresence
                .deliverAuthorizationRequest(request, contact);
        }
        else
        {
            //if we got here then "to" is simply someone in our contact
            //list so let's just simulate a reciproce request and generate
            //a response accordingly.

            //pretend that the remote contact is asking for authorization
            authorizationHandler.processAuthorisationRequest(
                request, contact);

            //and now pretend that the remote contact has granted us
            //authorization
            return new AuthorizationResponse(AuthorizationResponse.ACCEPT
                                            , "You are welcome!");
        }
    }
}

We have three cases in here:

  1. Another protocol provider would like to add us to their contact list - in this case we check whether we have them in our contact list and if not we add them as a non-persistent contact, then we ask the user to act on the authorization request and we return their response.
  2. The second case is actually the opposite of the first one. The user id being added to the contact list is not our own so we try to find whether another provider has that id, if they do, then we call their deliverAuthorizationRequest() method and it would execute its case 1
  3. The third case actually treats all the bogus contacts that a user can add in the Gibberish protocol and it simply simulates an authorization request response exchange by actually echo-ing the actions of the user.

Once a contact has been added to our contact list, it would either always have the same status as us (if it represents a bogus contact) or, in case it corresponds to another Gibberish account, it would have the status of its corresponding prtocol provider. In all cases status changes only come from one place - the publishPresenceStatus() method:

public void publishPresenceStatus(PresenceStatus status,
                                  String statusMessage) throws
    IllegalArgumentException, IllegalStateException,
    OperationFailedException
{
    PresenceStatus oldPresenceStatus = this.presenceStatus;
    this.presenceStatus = status;
    this.statusMessage = statusMessage;

    this.fireProviderStatusChangeEvent(oldPresenceStatus);

    //since we are not a real protocol, we set the contact presence status
    //ourselves and make them have the same status as ours.
    changePresenceStatusForAllContacts( getServerStoredContactListRoot()
                                        , getPresenceStatus());

    //now check whether we are in someone else's contact list and modify
    //our status there
    List contacts = findContactsPointingToUs();

    Iterator contactsIter = contacts.iterator();
    while (contactsIter.hasNext())
    {
        ContactGibberishImpl contact
            = (ContactGibberishImpl) contactsIter.next();

        PresenceStatus oldStatus = contact.getPresenceStatus();
        contact.setPresenceStatus(status);
        contact.getParentPresenceOperationSet()
            .fireContactPresenceStatusChangeEvent(
                contact
                , contact.getParentContactGroup()
                , oldStatus);

    }
}

Every time this method is called, we do 2 things: first we go through all the bogus contacts and change their status to match ours and then we look for all the providers that have us in their contact list and we change the status of the contact that represents us there.

And that’s pretty much it …

Unresolved contacts

As I mentioned unresolved contacts are created by the meta contact list when it loads its locally stored contact list. Since we have no server in Gibberish, we will trust the MetaContactList and assume that if it creates an unresolved contact it is because the contact had previously existed and we would immediately resolve it. Here’s what this gives us for the createUnresolvedContact() method:

public Contact createUnresolvedContact(String address,
                                       String persistentData,
                                       ContactGroup parent)
{
    ContactGibberishImpl contact = new ContactGibberishImpl(
        address
        , parentProvider);
    contact.setResolved(false);

    ( (ContactGroupGibberishImpl) parent).addContact(contact);

    fireSubscriptionEvent(contact,
                          parent,
                          SubscriptionEvent.SUBSCRIPTION_CREATED);

    //since we don't have any server, we'll simply resolve the contact
    //ourselves as if we've just received an event from the server telling
    //us that it has been resolved.
    fireSubscriptionEvent(
        contact, parent, SubscriptionEvent.SUBSCRIPTION_RESOLVED);

    //since we are not a real protocol, we set the contact presence status
    //ourselves
    changePresenceStatusForContact( contact, getPresenceStatus());

    return contact;
}

Simple isn’t it?!

previous | TOC | next