Example 5 - Spell Check Service Bundle

In the previous example, we created a service client that dynamically monitored the service's availability as a way to achieve more robustness. In this example, we complicate things further by defining a new service that uses an arbitrary number of our dictionary services to perform its function. More precisely, our spell check service will aggregate all dictionary services and provide another service that allows us to spell check passages using our underlying dictionary services to verify the spelling of words. Our bundle will only provide the spell check service if there is at least one dictionary service available. First, we will start by defining the spell check service interface in a file called SpellCheckService.java:

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

package tutorial.example5.service;

/**
 * A simple service interface that defines a spell check service.
 * A spell check service checks the spelling of all words in a
 * given passage. A passage is any number of words separated by
 * a space character and the following punctuation marks: comma,
 * period, exclamation mark, question mark, semi-colon, and colon.
**/
public interface SpellCheckService
{
    /**
     * Checks a given passage for spelling errors. A passage is any
     * number of words separated by a space and any of the following
     * punctuation marks: comma (,), period (.), exclamation mark (!),
     * question mark (?), semi-colon (;), and colon(:).
     * @param passage the passage to spell check.
     * @return An array of misspelled words or null if no
     *         words are misspelled.
    **/
    public String[] check(String passage);
}

The service interface is quite simple, with only one method that needs to be implemented. Notice that we put the service interface in the package tutorial.example5.service, instead of just putting it in tutorial.example5. We did this because we need to share the interface definition with other bundles, therefore it is better to separate service interfaces that need to be shared from code that does not need to be shared. Such an approach ensures a strong separation between interface and implementation.

In the following bundle source code, the bundle needs to create a complete list of all dictionary services; this is somewhat tricky and must be done carefully. First, the bundle uses its bundle context to register itself as a service event listener, then it queries for all currently available dictionary services. The bundle must register as a listener first to ensure that it will not miss any services that arrive dynamically and must query to get all services that have already arrived. Even then, the bundle must check for duplicates due to race conditions. After creating the list of dictionary services, the bundle then registers its spell check service if and only if there is at least one dictionary service available. Additionally, since the bundle is monitoring the dynamic availability of the dictionary services, when the number of dictionary services falls to zero or increases from zero, the bundle must unregister and register its spell check service, respectively. We implement our bundle 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.example5;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceEvent;

import tutorial.example2.service.DictionaryService;
import tutorial.example5.service.SpellCheckService;

/**
 * This class implements a bundle that implements a spell
 * check service. The spell check service uses all available
 * dictionary services to check for the existence of words in
 * a given sentence. This bundle not only monitors the dynamic
 * availability of dictionary services, but it manages the
 * aggregation of all available dictionary services as they
 * arrive and depart. The spell check service is only registered
 * if there are dictionary services available, thus the spell
 * check service will appear and disappear as dictionary
 * services appear and disappear, respectively.
**/
public class Activator implements BundleActivator, ServiceListener
{
    // Bundle's context.
    private BundleContext m_context = null;
    // List of available dictionary service references.
    private ArrayList m_refList = new ArrayList();
    // Maps service references to service objects.
    private HashMap m_refToObjMap = new HashMap();
    // The spell check service registration.
    private ServiceRegistration m_reg = null;

    /**
     * Implements BundleActivator.start(). Adds itself
     * as a service listener and queries for all currently
     * available dictionary services. Any available dictionary
     * services are added to the service reference list. If
     * dictionary services are found, then the spell check
     * service is registered.
     * @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=" + DictionaryService.class.getName() + ")" +
            "(Language=*))");

        // Query for all dictionary services.
        ServiceReference[] refs = m_context.getServiceReferences(
            DictionaryService.class.getName(), "(Language=*)");

        // Add any dictionaries to the service reference list.
        if (refs != null)
        {
            // Lock the list.
            synchronized (m_refList)
            {
                for (int i = 0; i < refs.length; i++)
                {
                    // Get the service object.
                    Object service = m_context.getService(refs[i]);

                    // Make that the service is not being duplicated.
                    if ((service != null) &&
                        (m_refToObjMap.get(refs[i]) == null))
                    {
                        // Add to the reference list.
                        m_refList.add(refs[i]);
                        // Map reference to service object for easy look up.
                        m_refToObjMap.put(refs[i], service);
                    }
                }

                // Register spell check service if there are any
                // dictionary services.
                if (m_refList.size() > 0)
                {
                    m_reg = m_context.registerService(
                        SpellCheckService.class.getName(),
                        new SpellCheckServiceImpl(), null);
                }
            }
        }
    }

    /**
     * Implements BundleActivator.stop(). Does nothing since
     * the framework will automatically unregister any registered services,
     * release any used services, and remove any event listeners.
     * @param context the framework context for the bundle.
    **/
    public void stop(BundleContext context)
    {
        // NOTE: The services automatically released.
    }

    /**
     * Implements ServiceListener.serviceChanged(). Monitors
     * the arrival and departure of dictionary services, adding and
     * removing them from the service reference list, respectively.
     * In the case where no more dictionary services are available,
     * the spell check service is registered. As soon as any dictionary
     * spell check becomes available, the spell check service is
     * reregistered.
     * @param event the fired service event.
    **/
    public void serviceChanged(ServiceEvent event)
    {
        String[] objectClass =
            (String[]) event.getServiceReference().getProperty("objectClass");

        // Add the new dictionary service to the service list.
        if (event.getType() == ServiceEvent.REGISTERED)
        {
            synchronized (m_refList)
            {
                // Get the service object.
                Object service = m_context.getService(event.getServiceReference());

                // Make that the service is not being duplicated.
                if ((service != null) &&
                    (m_refToObjMap.get(event.getServiceReference()) == null))
                {
                    // Add to the reference list.
                    m_refList.add(event.getServiceReference());
                    // Map reference to service object for easy look up.
                    m_refToObjMap.put(event.getServiceReference(), service);

                    // Register spell check service if necessary.
                    if (m_reg == null)
                    {
                        m_reg = m_context.registerService(
                            SpellCheckService.class.getName(),
                            new SpellCheckServiceImpl(), null);
                    }
                }
                else if (service != null)
                {
                    m_context.ungetService(event.getServiceReference());
                }
            }
        }
        // Remove the departing service from the service list.
        else if (event.getType() == ServiceEvent.UNREGISTERING)
        {
            synchronized (m_refList)
            {
                // Make sure the service is in the list.
                if (m_refToObjMap.get(event.getServiceReference()) != null)
                {
                    // Unget the service object.
                    m_context.ungetService(event.getServiceReference());
                    // Remove service reference.
                    m_refList.remove(event.getServiceReference());
                    // Remove service reference from map.
                    m_refToObjMap.remove(event.getServiceReference());

                    // If there are no more dictionary services,
                    // then unregister spell check service.
                    if (m_refList.size() == 0)
                    {
                        m_reg.unregister();
                        m_reg = null;
                    }
                }
            }
        }
    }

    /**
     * A private inner class that implements a spell check service;
     * see SpellCheckService for details of the service.
    **/
    private class SpellCheckServiceImpl implements SpellCheckService
    {
        /**
         * Implements SpellCheckService.check(). Checks the
         * given passage for misspelled words.
         * @param passage the passage to spell check.
         * @return An array of misspelled words or null if no
         *         words are misspelled.
        **/
        public String[] check(String passage)
        {
            // No misspelled words for an empty string.
            if ((passage == null) || (passage.length() == 0))
            {
                return null;
            }

            ArrayList errorList = new ArrayList();

            // Tokenize the passage using spaces and punctionation.
            StringTokenizer st = new StringTokenizer(passage, " ,.!?;:");

            // Lock the service list.
            synchronized (m_refList)
            {
                // Loop through each word in the passage.
                while (st.hasMoreTokens())
                {
                    String word = st.nextToken();

                    boolean correct = false;

                    // Check each available dictionary for the current word.
                    for (int i = 0; (!correct) && (i < m_refList.size()); i++)
                    {
                        DictionaryService dictionary =
                            (DictionaryService) m_refToObjMap.get(m_refList.get(i));

                        if (dictionary.checkWord(word))
                        {
                            correct = true;
                        }
                    }

                    // If the word is not correct, then add it
                    // to the incorrect word list.
                    if (!correct)
                    {
                        errorList.add(word);
                    }
                }
            }

            // Return null if no words are incorrect.
            if (errorList.size() == 0)
            {
                return null;
            }

            // Return the array of incorrect words.
            return (String[]) errorList.toArray(new String[errorList.size()]);
        }
    }
}

Note that we do not need to unregister the service in stop() method, because the OSGi framework will automatically do so for us. The spell check service that we have implemented is very simple; it simply parses a given passage into words and then loops through all available dictionary services for each word until it determines that the word is correct. Any incorrect words are added to an error list that will be returned to the caller. This solution is not optimal and is only intended for educational purposes. Next, we create a manifest.mf file that contains the metadata for our bundle:

Bundle-Activator: tutorial.example5.Activator
Import-Package: tutorial.example2.service
Export-Package: tutorial.example5.service
Bundle-Name: Spell check service
Bundle-Description: A bundle that implements a simple spell check service
Bundle-Vendor: Richard Hall
Bundle-Version: 1.0.0

We specify which class implements the Bundle-Activator interface and also that our bundle imports a shared package and exports a shared package for the dictionary service and spell check service, respectively.

To compile our source, we need to have the osgi.jar file (found in Oscar's lib 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 metadata 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 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 and Example 2b 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 service 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/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 install and start Oscar shell commands. To stop the bundle, use the stop Oscar shell command. Using the Oscar shell ps command to get the bundle identifier number for our spell check service bundle and we can stop and restart it at will using the stop and start commands, respectively. Using the services command, we can see which services are currently available in the OSGi framework, including our dictionary and spell check services. We can experiment with our spell check service's dynamic availability by stopping the dictionary service bundles; when both dictionary services are stopped, the services command will reveal that our bundle is no longer offering its spell check service. Likewise, when the dictionary services comeback, so will our spell check service. We create a client for our spell check service in Example 6. To exit Oscar, we use the shutdown command.