Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

Spell Checker Sample

This section presents a quick overview of the capabilities and usage of the DependencyManager java 5 annotations. In particular, we will recap the DependencyManager annotation architecture, and identify some  simple usage scenarios using a SpellChecker sample application with annotated components (the application is available from the felix trunk, in the dependencymanager/samples.annotation maven subproject).

...

Instead of writing Activators which extends the DependencyActivatorBase class, service components can now be annotated using the annotations provided by the org.apache.felix.dependencymanager.annotation bundle. Annotations are not reflectively parsed at runtime; but we use a BND plugin which scans annotations at compilation phase and generates a compact metadata file in the bundle's OSGIMETA-INF/dependencymanager subdirectory. This has the following benefits:

  • JVM startup speed is not affected, and class files are not parsed when the framework is starting
  • Moreover, since the annotations are not retained by the VM at runtime, it is not necessary to load the annotation API bundle at runtime.

At runtime, the metada metadata generated during the compilation phase are processed by a specific DependencyManager Runtime bundle, which is in charge of managing the service component lifecycle and dependencies. This Runtime bundle actually uses the DependencyManager programmatic API in order to manage the annotated components. Annotated components can then be inspected with the DependencyManager Gogo shell, as it is the case with DM components declared through the programmatic DM API.

...

To register a service, your can annotate your class with a @Service @Component annotation, and an instance of your class will be registered under all directly implemented interfaces into the OSGi registry. You can however take control on the interfaces to be exposed, and in this case, you can use the provides attribute, which takes a list of classes to expose from the registry.

To illustrate this, we are now introducing a SpellChecker application which provides a Felix "spellcheck" Gogo shell command. Gogo is the new shell supported by the Felix Framework. Our "spellcheck" command is implemented by the SpellChecker component which accepts a string as parameter. This string is then checked for proper existence. To do the checking, The SpellChecker class has a required/multiple (1..N) dependency over every available DictionaryService services. Such DictionaryService represents a real dictionary for a given langage language (it has a lang service property), and is configurable/instantiable from Configuration Admin.

...

Now we have introduced the background, here is the SpellCheck component:

Code Block
@Service@Component(provides={SpellChecker.class},
           properties={@Property(name=CommandProcessor.COMMAND_SCOPE, value="dmsample.annotation"),
                       @Property(name=CommandProcessor.COMMAND_FUNCTION, values={"spellcheck"})})
public class SpellChecker {
    // --- Gogo Shell command

    @Descriptor("checks if word is found from an available dictionary")
    public void spellcheck(@Descriptor("the word to check")String word) {
       // Check the proper existence of the word parameter, using injected DictionaryService instances
       // ...
    }
}

In the code above, you see that the SpellCheck is annotated with the @Service @Component annotation. Gogo runtime does not required shell commands to implement a specific interface. Commands just have to register some Pojos in the OSGi registry, but the only thing required is to provide the Pojos with two service properties ( COMMAND_SCOPE, and COMMAND_FUNCTION) which will be used by the Gogo runtime when instropecting the Pojo for invoking the proper functions.

...

And here is our previous SpellChecker component, augmented with two new ServiceDependency annotations:

Code Block
@Service@Component(provides={SpellChecker.class},
                   properties={@Property(name=CommandProcessor.COMMAND_SCOPE, value="dmsample.annotation"),
                                           @Property(name=CommandProcessor.COMMAND_FUNCTION, values={"spellcheck"})})
public class SpellChecker {
    @ServiceDependency(required = false)
    private LogService m_log;

    private CopyOnWriteArrayList<DictionaryService> m_dictionaries = new CopyOnWriteArrayList<DictionaryService>();

    @ServiceDependency(removed = "removeDictionary")
    protected void addDictionary(DictionaryService dictionary) {
        m_dictionaries.add(dictionary);
    }
    
    protected void removeDictionary(DictionaryService dictionary) {
        m_dictionaries.remove(dictionary);
    }

    // --- Gogo Shell command

    @Descriptor("checks if word is found from an available dictionary")
    public void spellcheck(@Descriptor("the word to check")String word)
    {
        m_log.log(LogService.LOG_INFO, "Checking spelling of word \"" + word
            + "\" using the following dictionaries: " + m_dictionaries);

        for (DictionaryService dictionary : m_dictionaries)
        {
            if (dictionary.checkWord(word))
            {
                System.out.println("word " + word + " is correct");
                return;
            }
        }
        System.err.println("word " + word + " is incorrect");
    }
}

...

Creating a Service from ConfigAdmin

The @Service @Component annotation is not the only one for creating services. Another one is the @FactoryConfigurationAdapterService annotation which allows to instantiate many instances of the same annotated service class from ConfigAdmin (and WebConsole). To illustrate this, let's take a look at our DictionaryImpl class which is part of the SpellChecker sample. This service is required by the SpellChecker component, when checking for proper word existence. And you can instantiate as many DictionaryService as you want, from ConfigAdmin ...

Code Block
@FactoryConfigurationAdapterService(factoryPid="DictionaryServiceFactoryDictionaryImplFactoryPid", updated="updated")  
public class DictionaryImpl implements DictionaryService {
    /**
     * We store all configured words in a thread-safe data structure, because ConfigAdmin
     * may invoke our updated method at any time.
     */
    private CopyOnWriteArrayList<String> m_words = new CopyOnWriteArrayList<String>();

    /**
     * Our Dictionary language.
     */
    private String m_lang;

    /**
     * Our service will be initialized from ConfigAdmin, and we also handle updates in this method.
     * @param config The configuration where we'll lookup our words list (key="words").
     */
    protected void updated(Dictionary<String, ?> config) {
        m_lang = (String) config.get("lang");
        m_words.clear();
        String[] words = (String[]) config.get("words");
        for (String word : words) {
            m_words.add(word);
        }
    }
           
    /**
     * Check if a word exists if the list of words we have been configured from ConfigAdmin/WebConsole.
     */
    public boolean checkWord(String word) {
        return m_words.contains(word);
    }
}

Our DictionaryImpl class implements a DictionaryService, and our class will be registered under that interface (all directly implemented interfaces are used when registering the service, but you can select some others using the provides attribute). The @FactoryConfigurationAdapterService annotation will instantiate our service for each configuration created from web console (and matching our "DictionaryServiceFactoryDictionaryImplFactoryPid" factoryPid).

We also use the updated attribute, which specifies a callback method which will handle properties configured by ConfigAdmin. The updated callback will also be called when our properties are changing. Every properties are propagated to our service properties, unless the properties starting with a dot ("."). Configuration properties starting with a dot (".") are considered private and are not propagated.

...

Code Block
  @FactoryConfigurationAdapterService(factoryPid="DictionaryServiceFactoryDictionaryImplFactoryPid",
    propagate=true,
    updated="updated",
    heading="Dictionary Services",
    description="Declare here some Dictionary instances, allowing to instantiates some DictionaryService services for a given dictionary language",
    metadata={
        @PropertyMetaData(
            heading="Dictionary Language",
            description="Declare here the language supported by this dictionary. " +
                "This property will be propagated with the Dictionary Service properties.",
            defaults={"en"},
            id="lang",
            cardinality=0),
        @PropertyMetaData(
            heading="Dictionary words",
            description="Declare here the list of words supported by this dictionary.",
            defaults={"hello", "world"},
            id="words",
            cardinality=Integer.MAX_VALUE)
    }
)  
public class DictionaryImpl implements DictionaryService {
    ... code same as before
}

...