Динамический выбор ссылок OSGi на основе свойств с использованием декларативных служб - PullRequest
1 голос
/ 12 апреля 2019

Я новичок в OSGi и быстро поражаюсь его сложностью. Я считаю, что это должно быть довольно просто, но мне не удалось найти полный рабочий пример того, чего я пытаюсь достичь.

У меня есть класс Java Foo, который содержит набор служб. Эти сервисы должны быть отфильтрованы на основе значения, характерного для данного конкретного экземпляра Foo. Может быть несколько экземпляров Foo, но у каждого должен быть свой набор отфильтрованных сервисов.

Для иллюстрации рассмотрим следующий пример (вдохновленный учебниками Apache Felix ):

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
}

После прочтения нескольких StackExchange сообщений и некоторых других статей кажется, что это можно сделать используя ConfigurationAdmin. Однако неясно, где именно и как следует использовать ConfigurationAdmin, особенно в отношении декларативных услуг. Я также прочитал и перечитал Спецификация службы администратора конфигурации , но я изо всех сил пытаюсь применить концепции.

Может ли кто-нибудь заполнить пробелы в моем понимании?

Заранее спасибо!


Редактировать

Ответ Кристиана помог мне подумать о декларативных услугах по-другому. Когда я возвращался к своим исследованиям, я снова наткнулся на сообщения Алана Хона в блоге на DZone . К сожалению, похоже, он так и не закончил серию, в которой обещал охватить поиск сервисов с использованием DS. Однако его пример исходного кода содержит следующее:

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!";
}

Это выглядит как жизнеспособное решение, но, похоже, не использует DS. Есть ли какие-то особые соображения с этим подходом? Я видел множество постов на SO и в других местах, в которых утверждается, что DS является панацеей от BundleContext#getServiceReferences, поэтому мне любопытно, можно ли / как это изменить для использования DS.

Ответы [ 2 ]

1 голос
/ 12 апреля 2019

Ваш код в main не имеет смысла.

Если вы создаете экземпляр компонента DS (декларативных служб) с ключевым словом new, тогда вся логика DS не будетбыть казненным.На самом деле в OSGi вы вообще не используете метод main ... возможно, для запуска инфраструктуры, но не для своей собственной логики.

Вы можете получить доступ к своей программе проверки орфографии, создав команду оболочки, которая ее использует, илисоздав службу http whiteboard, которая его использует.

Для настройки фильтра для ссылок на службы в SpellChecker вы можете использовать такую ​​конфигурацию, как:

pid: полное имя SpellChecker


dictionaryServices.target=(language=en)

Это установит SpellChecker для использования только английских словарей.

Дополнительные советы по DS можно найти в https://liquid -reality.de./2016/09/26/hints-ds.html

0 голосов
/ 12 апреля 2019

Как я понимаю, вы хотите иметь отношение 1: N между двумя компонентами.

С DS у вас есть пара альтернатив:

шаблон доски

Можно реализовать шаблон доски, где компонент 1 отслеживает регистрации служб OSGi DictionaryService.Компонент N регистрирует службы, и каждая регистрация служб перехватывается и используется зарегистрированным компонентом 1 .

Возможно, проблема в том, что вы не хотите активировать компонент 1 в производстве до тех пор, пока все ожидаемые компоненты N не зарегистрируют свои услуги и не отследят по 1 .

Используйте ссылку на множество элементов со сложнымВыражение фильтра

В конфигурации используется ссылка с несколькими значениями мощности и выражение фильтра, например: (| (language = en) (language = es))

Проблема та жекак с шаблоном доски.

Многие люди начинают писать «проверку работоспособности», где также определяется отношение 1: N, и уведомляет программиста, если запущены не все службы (или запрещает приложение быть доступным для пользователя),Проблема с подходом проверки работоспособности заключается в том, что у программиста должна быть та же логическая избыточность в системе.

Вместо DS используйте ECM (другую модель компонентов для OSGi)

Несмотря на то, что эталонный DS-шаблон и шаблон доски обеспечивают очень удобную гибкость во время разработки, он часто не подходит для производства, когда все службы должны быть внедрены до того, как приложение станет доступно пользователям.

ECM поддерживает отношение 1: N следующим образом:

  • В компоненте 1 вы можете определить массив фильтров
  • Компонент 1 будет удовлетворено только при наличии службы OSGi для всех указанных фильтров.

Поскольку область действия и цель компонента ECM очень похожи на область действия и цель компонента DS, люди, знающие DS, нуждаются в этом.только пару часов, чтобы выучить ECM.Поскольку ECM также зависит от служб OSGi, DS и ECM могут легко жить рядом друг с другом в одной системе и использовать службы OSGi, предоставляемые другой.

На основе вашего примера:

// All annotations from the ecm package

@Component
public class SpellChecker {

  @ServiceRef
  private DictionaryService[] dictionaryServices;

  // I think the language should be a parameter of your service function
  // rather than a member variable of your component class
  public boolean check(String word, String language) {
    if (word == null || word.isEmpty()) {
      return true;
    }

    if (dictionaryServices == null || dictionaryService.length = 0) {
      return false;
    }

    List<DictionaryService> ds = Arrays.asList(dictionaryServices);

    return ds.stream().anyMatch(dictionary -> dictionary.check(word));
  }

  // You need a setter in case of ECM and you can annotate the setter as well.
  // If you annotate the field instead, you need to specify the setter as an
  // attribute of the annotation
  @ServiceRef()
  public void setDictionaryServices(DictionaryService[] dictionaryServices) {
    this.dictionaryServices = dictionaryServices;
  }
}

Компонент выше может использоваться со следующим массивом строк в конфигурации:

dictionaryServices.target = [
                             "(language=en)",
                             "(language=de)",
                             "(language="es")"
                            ]

Компонент будет активирован, когда будут доступны все три ссылки, и вы получите массив служб словаря с тремяэлементы в нем (с тем же порядком в массиве, что и в конфигурации).

...