Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically choose OSGi references based on property using Declarative Services

I'm new to OSGi and am quickly becoming overwhelmed by its complexity. I believe this should be fairly simple, but I haven't been able to find a complete working example of what I'm trying to achieve.

I have a Java class Foo which contains a collection of services. These services need to be filtered based on a value which is specific to that particular instance of Foo. There can be multiple instances of Foo, but each one should have its own set of filtered services.

To illustrate, consider the following example (inspired by the Apache Felix tutorials):

public interface DictionaryService {
    public boolean check(String word);
}
@Component(property = "language=en")
public class EnglishDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"hi", "hello" /*...*/};

    @Override
    public boolean check(String word) {
        if (word == null || word.isEmpty()) {
            return true;
        }

        // super inefficient but you get the gist
        return Arrays.stream(WORDS).anyMatch(entry -> word.equalsIgnoreCase(entry));
    }
}
@Component(property = "language=en")
public class TexanDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"howdy" /*...*/};
    //...
}
@Component(property = "language=en")
public class AustralianDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"g'day" /*...*/};
    //...
}
@Component(property = "language=es")
public class SpanishDictionaryService implements DictionaryService {
    private static final String[] WORDS = {"hola" /*...*/};
    //...
}
@Component
public class SpellChecker {

    @Reference
    private volatile List<DictionaryService> dictionaryServices;

    public SpellChecker(String language) {
        // TODO: how to ensure my dictionaryServices match the given language code?
        // dictionaryServices.target = "(language=" + language + ")"
    }

    public boolean check(String word) {
        if (word == null || word.isEmpty()) {
            return true;
        }

        List<DictionaryService> ds = dictionaryServices;

        if (ds == null || ds.isEmpty()) {
            return false;
        }

        return ds.stream().anyMatch(dictionary -> dictionary.check(word));
    }
}
public static void main(String[] args) {
    SpellChecker englishChecker = new SpellChecker("en");
    SpellChecker spanishChecker = new SpellChecker("es");
    // do stuff
}

After reading through several StackExchange posts and some other articles, it seems this can be done using ConfigurationAdmin. However, it's unclear exactly where and how ConfigurationAdmin should be used, especially with regards to Declarative Services. I've also read and reread the Configuration Admin Service Specification, but I'm struggling to apply the concepts.

Can someone fill in the gaps in my understanding?

Thanks in advance!


Edit

Christian's answer helped me think about Declarative Services in a different way. As I was going back through my research, I came across Alan Hohn's blog posts on DZone again. Unfortunately, it seems he never finished his series which promised to cover service lookups using DS. However, his example source code contains the following:

public String greet(String language) {
    BundleContext context = FrameworkUtil.getBundle(this.getClass()).getBundleContext();

    String filter = "(language=" + language + ")";

    // Get ServiceReference(s) from OSGi framework, filtered for the specified value
    ServiceReference[] refs = null;
    try {
        refs = context.getServiceReferences(Greeter.class.getName(), filter);
        if (null != refs) {
            Greeter greeter = (Greeter)context.getService(refs[0]);
            return greeter.greet();
        }
    } catch (InvalidSyntaxException e) {
        LOGGER.error("Invalid query syntax", e);
    }
    LOGGER.warn("No plugins found, making the default greeting");
    return "Hello from the greeter manager!";
}

This looks like a viable solution, but it doesn't appear to use DS. Are there any special considerations with this approach? I've seen plenty of posts on SO and elsewhere which claim DS is the cure-all for BundleContext#getServiceReferences, so I'm curious if/how this could be refactored to use DS.

like image 226
roadkill Avatar asked Jun 08 '26 06:06

roadkill


1 Answers

Your code in main does not make sense.

If you create the instance of a (declarative services) DS component with the new keyword then the whole DS logic will not be executed. Actually in OSGi you do not use a main method at all... maybe for starting the framework but not for your own logic.

You can access your spell checker by creating a shell command that uses it or by creating an http whiteboard service that uses it.

For setting the filter for the service references in SpellChecker you could use a configuration like:

pid: fully qualified name of SpellChecker


dictionaryServices.target=(language=en)

This would set the SpellChecker to use only the English dictionaries.

For some more tips about DS you can refer to https://liquid-reality.de/2016/09/26/hints-ds.html

like image 110
Christian Schneider Avatar answered Jun 10 '26 17:06

Christian Schneider



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!