Example 6 - Spell Check Client Bundle

In this example we create a client for the spell check service we implemented in Example 5. This client monitors the dynamic availability of the spell check service and is very similar in structure to the dictionary client we implemented in Example 4. The functionality of the spell check client reads passages from standard input and spell checks them using the spell check service. Our bundle uses its bundle context to register itself as a service event listener; monitoring service events allows the bundle to monitor the dynamic availability of the spell check service. Our client uses the first spell check service it finds. The source code for our bundle is as follows in a file called Activator.java:

/*
 * OSGi and Gravity Service Binder tutorial.
 * Copyright (c) 2003  Richard S. Hall
 * http://oscar-osgi.sourceforge.net
**/

package tutorial.example6;

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.framework.ServiceReference;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;

import tutorial.example5.service.SpellCheckService;

/**
 * This class implements a bundle that uses a spell check
 * service to check the spelling of a passage. This bundle
 * is essentially identical to Example 4, in that it monitors
 * the dynamic availability of the spell check service.
 * When starting this bundle, the thread calling the start()
 * method is used to read passages from standard input. You can
 * stop spell checking passages by entering an empty line, but
 * to start spell checking again you must stop and then restart
 * the bundle.
**/
public class Activator implements BundleActivator, ServiceListener
{
    // Bundle's context.
    private BundleContext m_context = null;
    // The service reference being used.
    private ServiceReference m_ref = null;
    // The service object being used.
    private SpellCheckService m_checker = null;

    /**
     * Implements BundleActivator.start(). Adds itself
     * as a listener for service events, then queries for all
     * available spell check services. If none are found it goes
     * into its normal "passage checking loop" and waits for a
     * spell check service to arrive. Once it has a spell check
     * service it reads passages from standard input and checks
     * their spelling using the spell check service.
     * (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;

        // Listen for events pertaining to dictionary services.
        m_context.addServiceListener(this,
            "(objectClass=" + SpellCheckService.class.getName() + ")");

        // Query for a spell check service.
        m_ref = m_context.getServiceReference(SpellCheckService.class.getName());

        // If we found a spell check service, then get
        // a reference so we can use it.
        if (m_ref != null)
        {
            m_checker = (SpellCheckService) m_context.getService(m_ref);
        }

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

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

                // If the user entered a blank line, then
                // exit the loop.
                if (passage.length() == 0)
                {
                    break;
                }
                // If there is no spell checker, then say so.
                else if (m_checker == null)
                {
                    System.out.println("No spell checker available.");
                }
                // Otherwise check passage and print misspelled words.
                else
                {
                    String[] errors = m_checker.check(passage);

                    if (errors == null)
                    {
                        System.out.println("Passage is correct.");
                    }
                    else
                    {
                        System.out.println("Incorrect word(s):");
                        for (int i = 0; i < errors.length; i++)
                        {
                            System.out.println("    " + errors[i]);
                        }
                    }
                }
            }
        } 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)
    {
        // NOTE: The service is automatically released.
    }

    /**
     * Implements ServiceListener.serviceChanged(). Checks
     * to see if the service we are using is leaving or tries to get
     * a service if we need one.
     * @param event the fired service event.
    **/
    public void serviceChanged(ServiceEvent event)
    {
        String[] objectClass =
            (String[]) event.getServiceReference().getProperty("objectClass");

        // If a spell check service was registered, see if we
        // need one. If so, get a reference to it.
        if (event.getType() == ServiceEvent.REGISTERED)
        {
            if (m_ref == null)
            {
                // Get a reference to the service object.
                m_ref = event.getServiceReference();
                m_checker = (SpellCheckService) m_context.getService(m_ref);
            }
        }
        // If a spell check service was unregistered, see if it
        // was the one we were using. If so, unget the service
        // and try to query to get another one.
        else if (event.getType() == ServiceEvent.UNREGISTERING)
        {
            if (event.getServiceReference() == m_ref)
            {
                // Unget service object and null references.
                m_context.ungetService(m_ref);
                m_ref = null;
                m_checker = null;

                // Query to see if we can get another service.
                m_ref = m_context.getServiceReference(SpellCheckService.class.getName());
                if (m_ref != null)
                {
                    // Get a reference to the service object.
                    m_checker = (SpellCheckService) m_context.getService(m_ref);
                }
            }
        }
    }
}

The client listens for service events indicating that indicate the arrival or departure of spell check services. If a new spell check service arrives, then the bundle will start using that service if and only if it currently does not have a spell check service. If an existing spell check service disappears, then the bundle will check to see if the disappearing service is the one it is using; if it is it stops using it and tries to query for another spell check service, otherwise it ignores the event.

Like normal, we must create a manifest.mf file that contains the metadata for our bundle; the manifest file contains the following:

Bundle-Activator: tutorial.example6.Activator
Import-Package: tutorial.example5.service
Bundle-Name: Spell check client
Bundle-Description: A bundle that uses the spell check service
Bundle-Vendor: Richard Hall
Bundle-Version: 1.0.0

We specify which class implements the Bundle-Activator interface and also specify that our class imports the shared package of the spell check service interface using the Import-Package attribute. This import clause will be fulfilled by the bundle in Example 5, which exports the package that this bundle is importing. The OSGi framework will automatically handle the details of resolving import packages.

To compile our source code, we need to have the osgi.jar file (found in Oscar's lib directory) and the example5.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\example6, 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 metadata to the JAR file. To create the JAR file, we issue the command:

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

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

To run Oscar, we follow the instructions described in usage.html. When we start Oscar, it asks for a profile name, we will put all of our bundles in a profile named tutorial. After running Oscar, we should stop all tutorial bundles except for the service bundles. Use the ps command to make sure that only the bundles from Example 2, Example 2b, and Example 5 are active; use the start and stop commands as appropriate to start and stop the various tutorial bundles, respectively. (Note: Oscar uses some bundles to provide its command shell, so do not stop these bundles.) Now we can install and start our spell check client bundle. Assuming that we created our bundle in the directory c:\tutorial, we can install and start it in Oscar's shell using the following command:

    start file:/c:/tutorial/example6.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 install and start Oscar shell commands. When we start the bundle, it will use the shell thread to prompt us for passages; a passage is a collection or words separate by spaces, commas, periods, exclamation points, question marks, colons, or semi-colons. Enter a passage and press the enter key to spell check the passage or enter a blank line to stop spell checking passages. To restart the bundle, we must use the Oscar shell ps 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.

Since this client monitors the dynamic availability of the spell check service, it is robust in the scenario where the spell check service suddenly departs. Further, when a spell check 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.