Apache Felix Tutorial Example 5

Example 5 - Service Tracker Dictionary Client Bundle

In Example 4, we created a more robust client bundle for our dictionary service. Due to the complexity of dealing with dynamic service availability, even that client may not sufficiently address all situations. To deal with this complexity the OSGi Alliance defined the ServiceTracker utility class back in 2001. In this example we create a client for the dictionary service that uses the ServiceTracker class to monitor the dynamic availability of the dictionary service, resulting in an even more robust client.

The functionality of the new dictionary client is essentially the same as the one from Example 4. Our bundle uses its bundle context to create a ServiceTracker instance to track the dynamic availability of the dictionary service on our behalf. Our client uses the dictionary service return by the ServiceTracker, which is selected based on a ranking algorithm defined by the OSGi specification. The source code for our bundle is as follows in a file called Activator.java:

/*
 * Apache Felix OSGi tutorial.
**/

package tutorial.example5;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.util.tracker.ServiceTracker;

import tutorial.example2.service.DictionaryService;

/**
 * This class implements a bundle that uses a dictionary
 * service to check for the proper spelling of a word by
 * checking for its existence in the dictionary. This bundle
 * is more complex than the bundle in Example 3 because it
 * monitors the dynamic availability of the dictionary
 * services. In other words, if the service it is using
 * departs, then it stops using it gracefully, or if it needs
 * a service and one arrives, then it starts using it
 * automatically. As before, the bundle uses the first service
 * that it finds and uses the calling thread of the
 * start() method to read words from standard input.
 * You can stop checking words by entering an empty line, but
 * to start checking words again you must stop and then restart
 * the bundle.
**/
public class Activator implements BundleActivator
{
    // Bundle's context.
    private BundleContext m_context = null;
    // The service tacker object.
    private ServiceTracker m_tracker = null;

    /**
     * Implements BundleActivator.start(). Crates a service
     * tracker to monitor dictionary services and starts its "word
     * checking loop". It will not be able to check any words until
     * the service tracker find a dictionary service; any discovered
     * dictionary service will be automatically used by the client.
     * It reads words from standard input and checks for their
     * existence in the discovered dictionary.
     * (NOTE: It is very bad practice to use the calling thread
     * to perform a lengthy process like this; this is only done
     * for the purpose of the tutorial.)
     * @param context the framework context for the bundle.
    **/
    public void start(BundleContext context) throws Exception
    {
        m_context = context;

        // Create a service tracker to monitor dictionary services.
        m_tracker = new ServiceTracker(
            m_context,
            m_context.createFilter(
                "(&(objectClass=" + DictionaryService.class.getName() + ")" +
                "(Language=*))"),
            null);
        m_tracker.open();

        try
        {
            System.out.println("Enter a blank line to exit.");
            String word = "";
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

            // Loop endlessly.
            while (true)
            {
                // Ask the user to enter a word.
                System.out.print("Enter word: ");
                word = in.readLine();

                // Get the selected dictionary service, if available.
                DictionaryService dictionary = (DictionaryService) m_tracker.getService();

                // If the user entered a blank line, then
                // exit the loop.
                if (word.length() == 0)
                {
                    break;
                }
                // If there is no dictionary, then say so.
                else if (dictionary == null)
                {
                    System.out.println("No dictionary available.");
                }
                // Otherwise print whether the word is correct or not.
                else if (dictionary.checkWord(word))
                {
                    System.out.println("Correct.");
                }
                else
                {
                    System.out.println("Incorrect.");
                }
            }
        } catch (Exception ex) { }
    }

    /**
     * Implements BundleActivator.stop(). Does nothing since
     * the framework will automatically unget any used services.
     * @param context the framework context for the bundle.
    **/
    public void stop(BundleContext context)
    {
    }
}

Since this client uses the ServiceTracker utility class, it will automatically monitor the dynamic availability of the dictionary service. Like normal, we must create a manifest.mf file that contains the meta-data for our bundle:

Bundle-Name: Service Tracker-based dictionary client
Bundle-Description: A dictionary client using the Service Tracker.
Bundle-Vendor: Apache Felix
Bundle-Version: 1.0.0
Bundle-Activator: tutorial.example5.Activator
Import-Package: org.osgi.framework,
 org.osgi.util.tracker,
 tutorial.example2.service

We specify the class to activate our bundle via the Bundle-Activator attribute and also specify that our bundle imports the core OSGi framework package, the Service Tracker package, and the dictionary service interface package using the Import-Package attribute. The OSGi framework will automatically handle the details of resolving the bundle’s imported packages. (Note: Make sure your manifest file ends in a trailing carriage return or else the last line will be ignored.)

To compile our source code, we need to have the felix.jar file (found in Felix' bin directory) and the example2.jar file in our class path. We compile the source file using a command like:

javac -d c:\classes *.java

This command compiles all source files and outputs the generated classes into a subdirectory of the c:\classes directory; this subdirectory is tutorial\example5, named after the package we specified in the source file. For the above command to work, the c:\classes directory must exist. After compiling, we need to create a JAR file containing the generated package directories. We will also add our manifest file that contains the bundle’s meta-data to the JAR file. To create the JAR file, we issue the command:

jar cfm example5.jar manifest.mf -C c:\classes tutorial\example5

This command creates a JAR file using the manifest file we created and includes all of the classes in the tutorial\example5 directory inside of the c:\classes directory. Once the JAR file is created, we are ready to install and start the bundle.

To run Felix, we follow the instructions described in usage.html. When we start Felix, it asks for a profile name, we will put all of our bundles in a profile named tutorial. After running Felix, we should check that all tutorial bundles are stopped, except for the English dictionary service bundle from Example 2. We can use the Felix lb shell command to get a list of all bundles, their state, and their bundle identifier number. If the Example 2 bundle is not active, we should start the bundle using the start command along with the bundle’s identifier number displayed by the lb command and stop any other unneeded tutorial bundles using the stop command. (Note: Felix uses some bundles to provide its command shell, so do not stop these bundles.) Now we can install and start our dictionary client bundle. Assuming that we created our bundle in the directory c:\tutorial, we can install and start it in Felix' shell using the following command:

start file:/c:/tutorial/example5.jar

The above command installs and starts the bundle in a single step; it is also possible to install and start the bundle in two steps by using the Felix install and start shell commands. When we start the bundle, it will use the shell thread to prompt us for words. Enter one word at a time to check the words and enter a blank line to stop checking words. To restart the bundle, we must use the Felix shell lb command to get the bundle identifier number for the bundle and first use the stop command to stop the bundle, then the start command to restart it. To test the dictionary service, enter any of the words in the dictionary (e.g., "welcome", "to", "the", "OSGi", "tutorial") or any word not in the dictionary.

Since this client uses the ServiceTracker class, it is robust in the face of sudden departures of the the dictionary service. Further, when a dictionary service arrives, it automatically gets the service if it needs it and continues to function. These capabilities are a little difficult to demonstrate since we are using a simple single-threaded approach, but in a multi-threaded or GUI-oriented application this robustness is very useful.