The purpose of this example is to re-implement the spell check service in Example 5, but to do so using the Service Binder of the Gravity project; to complete this, we must download the Service Binder bundle. We will use this bundle for compiling as well as installing it into Oscar for running this example.
The spell check service of Example 5 was complex because it needed to aggregate all available dictionary services and monitor their dynamic availability. In addition, the spell check service's own availability was dependent upon the availability of dictionary services; in other words, the spell check service had a dynamic, one-to-many dependency on dictionary services. As it turns out, service dependencies are not managed at all by OSGi and end up being the responsibility of the application developer. The Gravity Service Binder tries to eliminate complex and error-prone service dependency handling by automating it for us. To do this, the Service Binder replaces our 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:
/* * OSGi and Gravity Service Binder tutorial. * Copyright (c) 2003 Richard S. Hall * http://oscar-osgi.sourceforge.net **/ package tutorial.example7; import org.ungoverned.gravity.servicebinder.GenericActivator; /** * This example re-implements the spell check service of * Example 5 using the Gravity 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 you 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 SpellCheckServiceImpl.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 metadata 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 metadata:
<?xml version="1.0" encoding="UTF-8"?> <bundle> <!-- -- This metadata file instructs the Gravity Service Binder to -- create one instance of "SpellCheckServiceImpl". It also -- tells the generic activator that this instance implements the -- "SpellCheckService" 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 check 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.example7.SpellCheckServiceImpl"> <service interface="tutorial.example5.service.SpellCheckService"/> <requires service="tutorial.example2.service.DictionaryService" filter="(Language=*)" cardinality="1..n" policy="dynamic" bind-method="addDictionary" unbind-method="removeDictionary" /> </instance> </bundle>
The above metadata tells the generic activator to create one instance of tutorial.example7.SpellCheckServiceImpl, which we will define next. The metadata 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 metadata 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 check service is very similar to the implementation in Example 5, but it is no longer implemented as an inner class of the activator. We define the new spell service in a file called SpellCheckServiceImpl.java as follows:
/* * OSGi and Gravity Service Binder tutorial. * Copyright (c) 2003 Richard S. Hall * http://oscar-osgi.sourceforge.net **/ package tutorial.example7; 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.example5.service.SpellCheckService; /** * This class re-implements the spell check service of Example 5. * This service implementation behaves exactly like the one in * Example 5, specifically, it aggregates all available dictionary * services, monitors their dynamic availability, and only offers * the spell check 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; intead, the metadata.xml * file describes the service dependencies to the Service Binder, which * automatically manages them and it also automatically registers the * spell check services as appropriate. **/ public class SpellCheckServiceImpl implements SpellCheckService { // 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 check service. * @param dictionary the dictionary to add to the spell * check 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 check service. * @param dictionary the dictionary to remove from the spell * check 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 5. 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 metadata for our bundle; the file contains this:
Bundle-Activator: tutorial.example7.Activator Import-Package: tutorial.example2.service, tutorial.example5.service, org.ungoverned.gravity.servicebinder Bundle-Name: Service Binder Spell check service Bundle-Description: A bundle that implements a simple spell check service Bundle-Vendor: Richard Hall Bundle-Version: 1.0.0 Metadata-Location: tutorial/example7/metadata.xml
We specify which class implements the Bundle-Activator interface and also specify that our class imports the shared packages of the spell check and dictionary interfaces, as well as the the package of the Service Binder.
To compile our source code, we must include the osgi.jar file (found in Oscar's lib directory), the servicebinder.jar file, and the example5.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\example7, 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 metadata file, called metadata.xml above, into the example's 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\example7. Now we can create the JAR file for our bundle using the following command:
jar cfm example7.jar manifest.mf -C c:\classes tutorial\example7
This command creates a JAR file using the manifest file we created and includes all of the classes and resources in the tutorial\example7 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.) 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 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/example7.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. This bundle will work with the spell check client bundle that we created in Example 6, so we should feel free to experiment. To exit Oscar, we use the shutdown command.
Note: The spell check service client bundle in Example 6 could also be re-implemented using the Service Binder approach outlined in this example. The spell check service client has a one-to-one, dynamic service dependency on the spell check 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.