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

previous | TOC | next

Implementing a Protocol

Writing a ProtocolProviderFactory

A ProtocolProviderFactory implementation is the class that creates protocol providers. Jitsi needs to create one protocol provider instance per user account. It will do so through the installAccount() method of ProtocolProviderFactoryGibberishImpl. Any factory must be extending the ProtocolProviderFactory abstract class.

public AccountID installAccount( String userIDStr,
                                 Map accountProperties)
{
    BundleContext context
        = GibberishActivator.getBundleContext();
    if (context == null)
        throw new NullPointerException("The specified BundleContext was null");

    if (userIDStr == null)
        throw new NullPointerException("The specified AccountID was null");

    if (accountProperties == null)
        throw new NullPointerException("The specified property map was null");

    accountProperties.put(USER_ID, userIDStr);

    AccountID accountID = new GibberishAccountID(userIDStr, accountProperties);

    //make sure we haven't seen this account id before.
    if (registeredAccounts.containsKey(accountID))
        throw new IllegalStateException(
            "An account for id " + userIDStr + " was already installed!");

    //first store the account and only then load it as the load generates
    //an osgi event, the osgi event triggers (through the UI) a call to the
    //ProtocolProviderService.register() method and it needs to acces
    //the configuration service and check for a stored password.
    this.storeAccount(
        GibberishActivator.getBundleContext()
        , accountID);

    accountID = loadAccount(accountProperties);

    return accountID;
}

Note that the installAccount and uninstallAccount methods, have to be persistent. In other words, once our factory creates an account it needs to remain there even after we restart the application. To achieve this we use the storeAccount() and removeStoredAccount() methods that are defined in the abstract protocol provider factory itself.

Accounts that we store through this storeAccount() method are going to be reloaded every time Jitsi is started until we remove them through removeStoredAccount().

In the installAccount() method we are using a loadAccount() method that would actually instantiate the protocol provider service and register it in the OSGi bundle context so that it is available for other bundles. Every time we start Jitsi the loadAccount() method will be called for all stored accounts. Before seeing the method we’ll define a hashtable where we’ll be storing references to all accounts we’ve created.

/**
 * The table that we store our accounts in.
 */

private Hashtable registeredAccounts = new Hashtable();

and here’s how the loadAccount() method itself looks for us.

public AccountID loadAccount( Map accountProperties)
{
    BundleContext context
        = GibberishActivator.getBundleContext();
    if(context == null)
        throw new NullPointerException("The specified BundleContext was null");

    String userIDStr = (String)accountProperties.get(USER_ID);

    AccountID accountID = new GibberishAccountID(userIDStr, accountProperties);

    //get a reference to the configuration service and register whatever
    //properties we have in it.

    Hashtable properties = new Hashtable();
    properties.put(PROTOCOL, ProtocolNames.JABBER);
    properties.put(USER_ID, userIDStr);

    ProtocolProviderServiceGibberishImpl gibberishProtocolProvider
        = new ProtocolProviderServiceGibberishImpl();

    gibberishProtocolProvider.initialize(userIDStr, accountID);

    ServiceRegistration registration
        = context.registerService( ProtocolProviderService.class.getName(),
                                   gibberishProtocolProvider,
                                   properties);

    registeredAccounts.put(accountID, registration);
    return accountID;
}

You’ll probably get an error for the above code on the line containing the ProtocolProviderServiceGibberishImpl instantiation, since we have not yet created it. You could ignore or comment it for now and we’ll get to it later.

Now, in order to make sure that all stored accounts are loaded every time Jitsi starts, it is our responsibility to call the method that does this. Its name is loadStoredAccounts(BundleContext) and it is implemented in the parent (abstract) protocol provider factory. It is however offering only protected access so we’ll add a convenience method here in order to be able and call it when our activator is start()ing. Here’s how this method looks:

/**
 * ...
 */

public void loadStoredAccounts()
{
    super.loadStoredAccounts( GibberishActivator.getBundleContext());
}

A protocol provider factory is also responsible for removing existing protocol providers. In its uninstallAccount() method it would close a correspnding protocol provider and remove its service registration from the OSGi bundle context. For our Gibberish protocol this would give us:

public boolean uninstallAccount(AccountID accountID)
{
    //unregister the protocol provider
    ServiceReference serRef = getProviderForAccount(accountID);

    ProtocolProviderService protocolProvider
        = (ProtocolProviderService) GibberishActivator.getBundleContext()
            .getService(serRef);

    try {
        protocolProvider.unregister();
    }
    catch (OperationFailedException exc) {           
        logger.error("Failed to unregister protocol provider for account : "
                + accountID + " caused by : " + exc);
    }

    ServiceRegistration registration
        = (ServiceRegistration)registeredAccounts.remove(accountID);

    if(registration == null)
        return false;

    //kill the service
    registration.unregister();

    return removeStoredAccount(
        GibberishActivator.getBundleContext()
        , accountID);
}

Once again, ignore the error caused by the reference to the not yet defice ProtocolProviderService class.

We have two more methods left to implement here: getRegisteredAccounts(), which would return a set over all the AccountID’s that and getProviderForAccount() which returns a reference to a protocol provider corresponding to a specified AccountID.

/**
 * ...
 */

public ServiceReference getProviderForAccount(AccountID accountID)
{
    ServiceRegistration registration
        = (ServiceRegistration)registeredAccounts.get(accountID);

    return (registration == null )
                ? null
                : registration.getReference();
}

/**
 * ...
 */

public ArrayList getRegisteredAccounts()
{
    return new ArrayList(registeredAccounts.keySet());
}

The complete source code for the Gibberish protocol provider factory is available here. You might also want to have a look at other factory implementations like for example ProtocolProviderFactoryJabberImpl and ProtocolProviderFactorySipImpl.

Now that we are ready with our factory we’ll add a few lines to the GibberishActivator so that it would export it as a service and load all stored accounts on startup. Here’s how this happens: First in GibberishActivator we define two more fields corresponding to a reference to the factory and its service registration

private        ServiceRegistration  gibberishPpFactoryServReg = null;
private static ProtocolProviderFactoryGibberishImpl
                                    gibberishProviderFactory  = null;

Exporting the service itself would happen in the start() method of the GibberishActivator and it would now look like this:

/**
 * ...
 */

public void start(BundleContext context)
    throws Exception
{
    this.bundleContext = context;

    Hashtable hashtable = new Hashtable();
    hashtable.put(ProtocolProviderFactory.PROTOCOL, "Gibberish");

    gibberishProviderFactory = new ProtocolProviderFactoryGibberishImpl();

    //load all jabber providers
    gibberishProviderFactory.loadStoredAccounts();

    //reg the jabber account man.
    gibberishPpFactoryServReg =  context.registerService(
                ProtocolProviderFactory.class.getName(),
                gibberishProviderFactory,
                hashtable);

    logger.info("Gibberish protocol implementation [STARTED].");
}

We’ve taken care about starting the bundle now let’s see how we handle stop()-ing it. Inside ProtocolProviderFactoryGibberishImpl we add a very simple stop() method in which we unregister all services and remove them from our accounts table.

/**
 * Prepares the factory for bundle shutdown.
 */

public void stop()
{
    Enumeration registrations = this.registeredAccounts.elements();
    while(registrations.hasMoreElements())
    {
        ServiceRegistration reg = ((ServiceRegistration)registrations.nextElement());
        reg.unregister();
    }

    Enumeration idEnum = registeredAccounts.keys();
    while(idEnum.hasMoreElements())
    {
        registeredAccounts.remove(idEnum.nextElement());
    }
}

Of course we’d also need to call this method from the GibberishActivator.stop() method. Here’s how we modify it:

public void stop(BundleContext context)
    throws Exception
{
    this.gibberishProviderFactory.stop();
    gibberishPpFactoryServReg.unregister();
    logger.info("Gibberish protocol implementation [STOPPED].");
}

So much for the factory. In case you are missing something, don’t hesitate to have a look at the real thing - our own ProtocolProviderFactoryGibberishImpl

PENDING: Move the following to the account wizard chapter since it only concerns the wizard. The parent factory also provides methods for storing a user’s password. These passwords are not securely stored (for now) so make sure that you only store them when the user has requested you to do so (the user credentials that you retrieved through a security authority had the persistentPassword property set to true).

previous | TOC | next