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.