Example 6 - Spell Checker Service Bundle
(This example should be rewritten to use the Service Tracker.)
In this example, we complicate things further by defining a new service that uses an arbitrary number of dictionary services to perform its function. More precisely, we define a spell checker 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 checker service if there is at least one dictionary service available. First, we will start by defining the spell checker service interface in a file called SpellChecker.java
:
/* * Apache Felix OSGi tutorial. **/ package tutorial.example6.service; /** * A simple service interface that defines a spell checker service. * A spell checker 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 SpellChecker { /** * 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.example6.service
, instead of just putting it in tutorial.example6
. 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. After creating the list of dictionary services, the bundle then registers its spell checker service if and only if there is at least one dictionary service available. These actions must be performed in a synchronized block to avoid interference from service events. 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 checker service, respectively. We implement our bundle in a file called Activator.java
:
/* * Apache Felix OSGi tutorial. **/ package tutorial.example6; 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.example6.service.SpellChecker; /** * This class implements a bundle that implements a spell * checker service. The spell checker 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 checker service is only registered * if there are dictionary services available, thus the spell * checker 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 checker 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 checker * service is registered. * @param context the framework context for the bundle. **/ public void start(BundleContext context) throws Exception { m_context = context; synchronized (m_refList) { // 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) { 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 checker service if there are any // dictionary services. if (m_refList.size() > 0) { m_reg = m_context.registerService( SpellChecker.class.getName(), new SpellCheckerImpl(), 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 checker service is unregistered. As soon as any dictionary * service becomes available, the spell checker service is * reregistered. * @param event the fired service event. **/ public void serviceChanged(ServiceEvent event) { synchronized (m_refList) { // Add the new dictionary service to the service list. if (event.getType() == ServiceEvent.REGISTERED) { // 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 checker service if necessary. if (m_reg == null) { m_reg = m_context.registerService( SpellChecker.class.getName(), new SpellCheckerImpl(), null); } } else if (service != null) { m_context.ungetService(event.getServiceReference()); } } // Remove the departing service from the service list. else if (event.getType() == ServiceEvent.UNREGISTERING) { // 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 checker service. if (m_refList.size() == 0) { m_reg.unregister(); m_reg = null; } } } } } /** * A private inner class that implements a spell checker service; * see SpellChecker for details of the service. **/ private class SpellCheckerImpl implements SpellChecker { /** * Implements SpellChecker.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 checker 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 meta-data for our bundle:
Bundle-Name: Spell checker service Bundle-Description: A bundle that implements a simple spell checker service Bundle-Vendor: Richard Hall Bundle-Version: 1.0.0 Bundle-Activator: tutorial.example6.Activator Export-Package: tutorial.example6.service Import-Package: org.osgi.framework, tutorial.example2.service
We specify which class used to activate the bundle via the Bundle-Activator
attribute. Our bundle exports the spell checker service interface using the Export-Package
attribute and imports the OSGi core framework and dictionary service interface packages using the Import-Package
attribute. (Note: Make sure your manifest file ends in a trailing carriage return or else the last line will be ignored.)
To compile our source, 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\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 meta-data 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 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 stop all tutorial bundles except for the service bundles. Use the lb
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: Felix uses some bundles to provide its command shell, so do not stop these bundles.) Now we can install and start our spell checker service 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/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 Felix install
and start
shell commands. To stop the bundle, use the Felix stop
shell command. Using the Felix shell lb
command to get the bundle identifier number for our spell checker 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 checker services. We can experiment with our spell checker 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 checker service. Likewise, when the dictionary services comeback, so will our spell checker service. We create a client for our spell checker service in Example 7. To exit Felix, we use the shutdown
command.