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

previous | TOC | next

Implementing a Protocol

Implementing the ProtocolProviderService.

We are getting close to the essence here. The protocol provider service is the entry point to all protocol functionality. In here we will mainly have to do the following two things:

  1. Implement methods that manage our protocol connection (i.e. those that put our account online) and those that do the corresponding event dispatching (notify users of the service that the provider has become on/off line).
  2. Initialize and manage our operation sets. 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.

Let’s start by implementing the functionality from the above point one.

First of all we’d need to create our protocol provider service class. In conformity with naming conventions (i.e. ServiceName<ProtoName>Impl) our protocol provider service implementation will be called ProtocolProviderServiceGibberishImpl.

We’ll start by writing a few methods that would handle listeners and event dispatching. We’ll be storing registration listeners in the following Vector:

private Vector registrationStateListeners = new Vector();

We’ll have add and remove methods that would respectively register and unregister in this vector listeners interested in connection events:

/**
 * ...
 */

public void addRegistrationStateChangeListener(
    RegistrationStateChangeListener listener)
{
    synchronized(registrationStateListeners)
    {
        if (!registrationStateListeners.contains(listener))
            registrationStateListeners.add(listener);
    }
}

/**
 * ...
 */

public void removeRegistrationStateChangeListener(
    RegistrationStateChangeListener listener)
{
    synchronized(registrationStateListeners)
    {
        registrationStateListeners.remove(listener);
    }
}

We’ll also define a method that would actually traverse the listener list and dispatch registration events when they occur:

private void fireRegistrationStateChanged( RegistrationState oldState,
                                           RegistrationState newState,
                                           int               reasonCode,
                                           String            reason)
{
    RegistrationStateChangeEvent event =
        new RegistrationStateChangeEvent(
                        this, oldState, newState, reasonCode, reason);

    logger.debug("Dispatching " + event + " to "
                 + registrationStateListeners.size()+ " listeners.");

    //create an iterator over a copy of all registration listeners.only use
    //the copy to avoid problems that might occur while traversing the list.
    Iterator listeners = null;
    synchronized (registrationStateListeners)
    {
        listeners = new ArrayList(registrationStateListeners).iterator();
    }

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

        listener.registrationStateChanged(event);
    }
    logger.trace("Done.");
}

Now we are ready to add the method that would actually register (or connect) the gibberish protocol, thus making it ready to publish a specific presence status, receive that status for its contact list members, send messages and etc. The method is called (logically) register().

public void register(SecurityAuthority authority)
    throws OperationFailedException
{
    //we don't really need a password here since there's no server in
    //Gibberish but nevertheless we'll behave as if we did.

    //verify whether a password has already been stored for this account
    String password = GibberishActivator.
        getProtocolProviderFactory().loadPassword(getAccountID());

    //if we don't - retrieve it from the user through the security authority
    if (password == null)
    {
        //create a default credentials object
        UserCredentials credentials = new UserCredentials();
        credentials.setUserName(getAccountID().getUserID());

        //request a password from the user
        credentials = authority.obtainCredentials("Gibberish"
                                                  , credentials);

        //extract the password the user passed us.
        char[] pass = credentials.getPassword();

        // the user didn't provide us a password (canceled the operation)
        if (pass == null)
        {
            fireRegistrationStateChanged(
                getRegistrationState()
                , RegistrationState.UNREGISTERED
                , RegistrationStateChangeEvent.REASON_USER_REQUEST, "");
            return;
        }

        password = new String(pass);

        //if the user indicated that the password should be saved, we'll ask
        //the proto provider factory to store it for us.
        if (credentials.isPasswordPersistent())
        {
            GibberishActivator.getProtocolProviderFactory()
                .storePassword(getAccountID(), password);
        }
    }

    RegistrationState oldState = currentRegistrationState;
    currentRegistrationState = RegistrationState.REGISTERED;

    fireRegistrationStateChanged(
        oldState
        , currentRegistrationState
        , RegistrationStateChangeEvent.REASON_USER_REQUEST
        , null);
}

One particular thing about this method is the fact that it needs to retrieve a password before actually connecting to the network. Well, … Gibberish doesn’t really need a specific password but a normal protocol would so we also pretend we do. In Jitsi we retrieve passwords through a callback instance of the SecurityAuthority interface. This interface is implemented by the entity/bundle that is calling the register method. This entity is quite often the user interface. The UI would most of implement a security authority requestCredentials() method by showing a dialog and asking the use to enter a password. We will then get the password the user has entered or null if the user canceled the operation, in which case we should also cancel the entire registration process.

Another peculiar thing about the Gibberish protocol provider implementation is the fact that since there is no real protocol stack working behind the scenes, we don’t need to wait for a specific confirmation before notifying listeners that the registration has been successful. That’s why we fire the registration state chane event before returning from the register() method. Note that you should never do this in a real protocol implementation unless your protocol doesn’t require an initial registration and you are really certain this is the way to go. You should normally wait for an event notifying you of successful connection to the protocol server before trumpeting success to the registration listeners.

Since we have a register() method, we would quite naturally also have an unregister() one. Not much to do here apart from notifying listers (again this is not how things would have happened in a real world implementation).

public void unregister()
        throws OperationFailedException
{
    RegistrationState oldState = currentRegistrationState;
    currentRegistrationState = RegistrationState.UNREGISTERED;

    fireRegistrationStateChanged(
        oldState
        , currentRegistrationState
        , RegistrationStateChangeEvent.REASON_USER_REQUEST
        , null);
}

I am not going to explain in detail the rest of the methods such as getProtocolName(), getRegistrationState(), getProtocolIcon(), etc. You can simply have a look at the way they are implemented in our own ProtocolProviderServiceGibberishImpl

Operation Sets

As I mentioned earlier, operation sets are sets of functionalities that may be supported by some protocols and/or protocol implementations and not by others. Example operation sets include, chatting, typing notifications, telephony, presence, file transfer and others. A protocol provider without any operation sets is quite useless since it doesn’t do anything.

In Gibberish we currently implement some operation sets and we hope to one day implement all of them cause it would be quite useful for testing. Until then here’s how we do it for those that work already:

We instantiate and initialize all existing operation sets in the initialize method of a protocol provider. This initialize() method is call in the protocol provider service constructor, so that the operation sets could be up and running by the time the protocol provider service is registered in the OSGi bundle context. Here’s how our initialize() method currently looks:

protected void initialize(String userID,
                          AccountID accountID)
{
    synchronized(initializationLock)
    {
        this.accountID = accountID;

        //initialize the presence operationset
        OperationSetPersistentPresenceGibberishImpl persistentPresence =
            new OperationSetPersistentPresenceGibberishImpl(this);

        supportedOperationSets.put(
            OperationSetPersistentPresence.class.getName(),
            persistentPresence);

        //register it once again for those that simply need presence and
        //won't be smart enough to check for a persistent presence
        //alternative
        supportedOperationSets.put( OperationSetPresence.class.getName(),
                                    persistentPresence);

        //initialize the IM operation set
        OperationSetBasicInstantMessagingGibberishImpl basicInstantMessaging
            = new OperationSetBasicInstantMessagingGibberishImpl(
                this
                , (OperationSetPersistentPresenceGibberishImpl)
                        persistentPresence);

        supportedOperationSets.put(
            OperationSetBasicInstantMessaging.class.getName()
            , basicInstantMessaging);

        //initialize the typing notifications operation set
        OperationSetTypingNotifications typingNotifications =
            new OperationSetTypingNotificationsGibberishImpl(
                    this, persistentPresence);

        supportedOperationSets.put(
            OperationSetTypingNotifications.class.getName()
            , typingNotifications);

        isInitialized = true;
    }
}

We store initialized methods in a hashtable mapping them against the class name of the abstract operation set that they implement. Bundles that need to use them could retrieve them through the following getOperationSet() method.

public OperationSet getOperationSet(Class opsetClass)
{
    return (OperationSet) getSupportedOperationSets()
        .get(opsetClass.getName());
}

The rest of the Gibberish protocol provider service implementation is available here.

Now let’s have a look at some of the OperationSets.

previous | TOC | next