Example 10 - Spell Checker Service using Declarative Services

The purpose of this example is to re-implement the spell checker service in Example 6, but to do so using Declarative Services; to complete this, we must download the SCR bundle. The SCR bundle is needed to enable the functionality offered by Declarative Services at run time. Readers are also suggested have a look at OSGi Wiki on Declarative Services

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. Declarative Services allow to eliminate complex and error-prone service dependency handling by automating it. To do this, we provide an XML file that declare the exposed and consumed services. 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 implementation called SpellCheckerImpl.java:

package tutorial.example10;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import tutorial.example2.service.DictionaryService;
import tutorial.example6.service.SpellChecker;

/**
 * A class that implements a spell checker service; see
 * SpellChecker for details of the service.
 * <p>
 * The dependent services are injected through the
 * Declarative Services mechanism.
 **/
public class SpellCheckerImpl implements SpellChecker {

    private Collection dictionaries = new java.util.ArrayList();

    public void addDictionary(DictionaryService dict) {
        synchronized(this) {
            this.dictionaries.add(dict);
        }
    }

    public void removeDictionary(DictionaryService dict) {
        synchronized(this) {
            this.dictionaries.remove(dict);
        }
    }

    /**
     * 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;
        }

        List errorList = new java.util.ArrayList();

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

        // Lock the service list.
        synchronized (this) {
            // 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.
                Iterator iter = this.dictionaries.iterator();
                while (iter.hasNext()) {
                    DictionaryService dictionary = (DictionaryService)iter.next();
                    if (dictionary.checkWord(word)) {
                        correct = true;
                        break;
                    }
                }

                // 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 don't even need an Activator anymore although we can still provide one. We must still create a manifest.mf file that contains the meta-data for the bundle; the manifest file is as follows:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Spell checker service (DS)
Bundle-Description: A bundle that implements a simple spell checker service and uses declarative services
Bundle-Vendor: The Apache Software Foundation
Bundle-Version: 1.0.0
Import-Package: org.osgi.framework,
 org.osgi.service.component,
 osgi.tutorial.example2.service,
 osgi.tutorial.example6.service
Service-Component: OSGI-INF/spellchecker.xml

Notice that we import the org.osgi.service.component package. You'll also notice that there's no Bundle-Activator attribute because we don't need it in this example. At the bottom, we have the Service-Component entry where we specify the XML file we were talking about above. (Note: Make sure your manifest file ends in a trailing carriage return or else the last line will be ignored.) Of course, we also have to supply that XML file.

Create a new directory called OSGI-INF in the root of the project (next to META-INF). In the OSGI-INF directory place the following XML file we've named spellchecker.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component name="tutorial.example10.spellchecker" immediate="true">
    <implementation class="tutorial.example10.SpellCheckerImpl"/>
    <service>
        <provide interface="tutorial.example6.service.SpellChecker"/>
    </service>
    <reference name="DICTIONARY"
        interface="tutorial.example2.service.DictionaryService"
        bind="addDictionary"
        unbind="removeDictionary"
        cardinality="1..n"
        policy="dynamic"/>
</component>

The XML basically tells the SCR bundle that the SpellChecker service is implemented by the SpellCheckerImpl class and that we need 1..n DictionaryService instances. You'll guess that the bind and unbind specify the method names that will be used by the SCR bundle to inject (and remove) the dependent services.

We'll skip all the details for compiling the new example as it's basically the same as for the previous examples. One particularity, though, is the OSGI-INF directory. You have to make sure it is included in the bundle.

To run this example, you may have to temporarily disable the Bundle-Activator for example 6 which contains the service interface and a service implementation. Otherwise, the OSGi container may use the service implementation of example 6 while you actually wanted to see the new implementation here in action.

Furthermore, you may have to restart the client before it can acquire the spell checker service, as the code
for the client is part of the bundle activator which interferes with service startup.

  • No labels