Example 8 - Spell Checker Service using Service Binder
[Note: The Service Binder was the original project to attempt to automate service dependency management for the OSGi platform and was the inspiration for Declarative Services introduced in OSGi R4. The Service Binder is no longer under active development, but this example is kept in the tutorial for historical purposes. New projects should consider using one of the other dependency injection technologies (e.g., Declarative Services, Dependency Manager, or iPOJO).]
The purpose of this example is to re-implement the spell checker service in Example 6, but to do so using the Service Binder; to complete this, we must download the Service Binder bundle. The Service Binder bundle is needed to compile the example and at run time to execute the example.
The spell checker service of Example 6 was complex because it needed to aggregate all available dictionary services and monitor their dynamic availability. In addition, the spell checker service's own availability was dependent upon the availability of dictionary services; in other words, the spell checker service had a dynamic, one-to-many dependency on dictionary services. As it turns out, service dependencies are not managed at all by the OSGi framework and end up being the responsibility of the application developer. The Service Binder tries to eliminate complex and error-prone service dependency handling by automating it. To do this, the Service Binder replaces the BundleActivator code with a generic bundle activator that parses an XML file that describes the instances we want to create and their service dependencies. Instead of writing a lot of complex code, we simply write a declarative XML file. For an example, consider the following code for the new service's bundle activator in a file called Activator.java
:
/* * Apache Felix OSGi tutorial. **/ package tutorial.example8; import org.apache.felix.servicebinder.GenericActivator; /** * This example re-implements the spell checker service of * Example 6 using the Service Binder. The Service Binder * greatly simplifies creating OSGi applications by * essentially eliminating the need to write OSGi-related * code; instead of writing OSGi code for your bundle, you * create a simple XML file to describe your bundle's service * dependencies. This class extends the generic bundle activator; * it does not provide any additional functionality. All * functionality for service-related tasks, such as look-up and * binding, is handled by the generic activator base class using * data from the metadata.xml file. All application * functionality is defined in the SpellCheckerImpl.java * file. **/ public class Activator extends GenericActivator { }
All custom functionality has been removed from the bundle activator, it is only necessary to subclass the generic activator exported by the Service Binder. The generic activator performs its task by parsing an XML meta-data file that describes what instances should be created and what their service dependencies are; for our example, we create a file called metadata.xml
that contains the instance and service dependency meta-data:
<?xml version="1.0" encoding="UTF-8"?> <bundle> <!-- -- This meta-data file instructs the Service Binder to -- create one instance of "SpellCheckerImpl". It also -- tells the generic activator that this instance implements the -- "SpellChecker" service interface and that it has an -- aggregate dependency on "DictionaryService" services. Since -- the service dependency on dictionary services has a lower -- cardinality of one, the generic activator will create the instance -- and offer its spell checker service only when there is at least -- one dictionary service available. The service dependency is -- "dynamic", which means that dictionary service availability -- will be monitored dynamically at runtime and it also tells the -- generic activator which methods to call when adding and removing -- dictionary services. --> <instance class="tutorial.example8.SpellCheckerImpl"> <service interface="tutorial.example6.service.SpellChecker"/> <requires service="tutorial.example2.service.DictionaryService" filter="(Language=*)" cardinality="1..n" policy="dynamic" bind-method="addDictionary" unbind-method="removeDictionary" /> </instance> </bundle>
The above meta-data tells the generic activator to create one instance of tutorial.example8.SpellCheckerImpl
, which we will define next. The meta-data also tells the generic activator that the instance has an aggregate service dependency (in this case, one-to-many) on dictionary services and that the services should be tracked dynamically. It also specifies the bind and unbind methods that should be called on the instance when dictionary services appear and disappear. It is important to understand that the generic activator is constantly trying to maintain the instances defined in the meta-data file. At any given point in time, a specific instance may be valid (if all service dependencies are satisfied) or invalid (if any service dependencies are unsatisfied), but at all times the generic activator is trying to get the declared instances into a valid state. The code for our new spell checker service is very similar to the implementation in Example 6, but it is no longer implemented as an inner class of the activator. We define the new spell checker service in a file called SpellCheckerImpl.java
as follows:
/* * Apache Felix OSGi tutorial. **/ package tutorial.example8; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.util.ArrayList; import java.util.StringTokenizer; import tutorial.example2.service.DictionaryService; import tutorial.example6.service.SpellChecker; /** * This class re-implements the spell checker service of Example 6. * This service implementation behaves exactly like the one in * Example 6, specifically, it aggregates all available dictionary * services, monitors their dynamic availability, and only offers * the spell checker service if there are dictionary services * available. The service implementation is greatly simplified, * though, by using the Service Binder. Notice that there is no OSGi * references in the application code; instead, the metadata.xml * file describes the service dependencies, which is read by the * Service Binder to automatically manage the dependencies and * register the spell checker service as appropriate. **/ public class SpellCheckerImpl implements SpellChecker { // List of service objects. private ArrayList m_svcObjList = new ArrayList(); /** * This method is used by the Service Binder to add * new dictionaries to the spell checker service. * @param dictionary the dictionary to add to the spell * checker service. **/ public void addDictionary(DictionaryService dictionary) { // Lock list and add service object. synchronized (m_svcObjList) { m_svcObjList.add(dictionary); } } /** * This method is used by the Service Binder to remove * dictionaries from the spell checker service. * @param dictionary the dictionary to remove from the spell * checker service. **/ public void removeDictionary(DictionaryService dictionary) { // Lock list and remove service object. synchronized (m_svcObjList) { m_svcObjList.remove(dictionary); } } /** * 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) { // 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_svcObjList) { // 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_svcObjList.size()); i++) { DictionaryService dictionary = (DictionaryService) m_svcObjList.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()]); } }
Notice how much simpler this service implementation is when compared to the same service implemented in Example 6. There are no references to OSGi interfaces in our application code and all tricky and complex code dealing with monitoring of services is handled for us. We must still create a manifest.mf
file that contains the meta-data for the bundle; the manifest file is as follows:
Bundle-Activator: tutorial.example8.Activator Import-Package: tutorial.example2.service, tutorial.example6.service, org.apache.felix.servicebinder Bundle-Name: Service Binder Spell checker service Bundle-Description: A bundle that implements a simple spell checker service Bundle-Vendor: Apache Felix Bundle-Version: 1.0.0 Metadata-Location: tutorial/example8/metadata.xml
We specify which class is used to activate the bundle via the Bundle-Activator
attribute and also specify that the bundle imports the spell checker, dictionary, and Service Binder packages. (Note: Make sure your manifest file ends in a trailing carriage return or else the last line will be ignored.)
To compile the source code, we must include the felix.jar
file (found in Felix' lib
directory), the servicebinder.jar file, the example2.jar file, and the example6.jar file in the 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\example8
, named after the package we specified in the source file. For the above command to work, the c:\classes
directory must exist.
Before we can create our bundle JAR file, we must copy the bundle's service dependency meta-data file, called metadata.xml
above, into the example class' package. Assuming that we used the above command to compile the bundle, then we should copy the metadata.xml
file into c:\classes\tutorial\example8
. Now we can create the JAR file for our bundle using the following command:
jar cfm example8.jar manifest.mf -C c:\classes tutorial\example8
This command creates a JAR file using the manifest file we created and includes all of the classes and resources in the tutorial\example8
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.) We must also install the servicebinder.jar
bundle that we downloaded at the beginning of this example. Assuming that we saved the bundle in our tutorial directory, we install the bundle using the following command:
install file:/c:/tutorial/servicebinder.jar
We do not need to start the Service Binder bundle, because it is only a library. 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/example8.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. Use the Felix shell lb
command to get the bundle identifier number for the spell checker service bundle to 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. This bundle will work with the spell checker client bundle that we created in Example 7, so feel free to experiment. To exit Felix, use the shutdown
command.
[Note: The spell checker client bundle in Example 7 could also be re-implemented using the Service Binder approach outlined in this example. The spell checker client has a one-to-one, dynamic service dependency on the spell checker service. Further, an entire application of instances could be described in a single metadata.xml
in a single bundle or across a collection of bundles and the Service Binder will automatically manage the service dependencies among them.]