Фабрика агентов
Из сообщения на запуск агента извлекается идентификатор агента. Идентификатор предается фабрике для получения java-объекта агента. Приложение должно иметь возможность заменять фабрику, чтобы получать или создавать объекты в соответствии с правилами конечного приложения.
Штатно в КМА должна быть фабрика для получения объектов, как Spring-бинов, т.к. именно такой подход используется в основном целевом приложении - CMJ. Идентификатор агента в таком случае есть идентификатор бина. Фабрика должна быть реализована в отдельном jar-модуле, чтобы иметь возможность подстриваться под версии Spring в конечном приложении, а также не включаться в приложения, не использующие Spring Framework.
Потенциальные вариации фабрик:
- инстанцирование объекта агента по имени класса
- для агентов, являющихся CDI-бинами (включая EJB)
Фабрика ExecutorService для исполнения агента по-расписанию
Для агентов по-расписанию требуется предоставить выделенный пул LongRunnig потоков. Для ASAP и Delayed другой пул - ShortRunnig. Активация выполняется через стандартный JRE интерфейс ExecutorService. Получение реализации интерфейса должно быть опосредовано, через фабрику, для гибкости встраивания в приложения, как и в случае фабрики агентов. Возможность переиспользования пула потока, который уже выделен приложением, необходима для управления потреблением ресурсов CPU приложением в целом. Также приложением может быть востребовано и обратное - выделение отдельного пула для выполнения агентов.
По умолчанию должна использоваться фабрика, использующая JRE Executors.newFixedThreadPool.
Также штатно в КМА должна быть фабрика для получения реализации ExecutorService средствами Spring Framework. По-умолчанию это бин с именем "executorService", но может быть и переопределен приложением. Реализации данной фабрики должна быть в том же jar-модуле, что и фабрика агентов для Spring. При наличии в приложении данного jar, дефолтная фабрика должна быть автоматически заменена на фабрику для Spring.
Потенциальные вариации фабрик:
- Инжектированный ManagedExecutorSevice для JavaEE приложений
- Получение через JNDI-looup
- Создание нового отдельного пула одним из перечисленных способов.
Активация группы агентов по расписанию
Задача в потоке пула LongRunning, получает JMS-сигнал на запуск группы агентов. В этом потоке необходимо поочереди запустить агенты, указанные в группе. Группа может состоять из "самостоятельных" агентов и агентов по "JMS-сообщениям"
Вариант с периодическим опросом через JMSConsumer.receive(timeout) вместо MDB или MessageListener, обусловлен в том числе и стратегией определения свободного сервера (см. п. Конкуренция)
Активация "самостоятельного" агента
Задача в потоке пула LongRunning, получив JMS-сигнал на запуск, должна получить реализацию агента через фабрику, подготовить параметры для агента и запустить агент на исполнение в текущем потоке.
Все агенты должны реализовывать интерфейс, через который КМА активирует агент и передает ему параметры. Прототип метода, через который активируется агент:
String agentRun(AgentContext<T> context, P parameters);
P - это generic-тип. Метод не соответствует интерфейсу Callable или Runnable, который требует ExecutorService, но это легко решается классом-адаптером.
Итак, КМА извлекает JSON с параметрами из сообщения и десериализует JSON в Java-объект класса P.
КМА запускает java-объект агента через полученный ExecutorService с передачей java-объекта с параметрами.
Активация агента, обрабатывающего JMS-сообщения
Перед запуском агента необходимо создать JMS-подписку на JMS-Queue или JMS-Topic (jmsContext.createConsumer и т.п.). Для возможности откатывать доставку сообщения при сбоях, необходимо, чтобы сессия с брокером была открыта в контексте транзакции (сначала userTransaction.begin, затем jmsContext.createConsumer). Получение менеджера транзакций должно быть абстрагировано от конкретной реализации, как и в случае фабрики объектов агентов.
Агент должен получать сообщения по одному, перебирая их через итератор, для чего в КМА должна быть реализация Iterable<E>. При активации агента, ему должен быть предан и данный Iterable. Запрос очередного сообщения агентом методами next, hasNext должен коммитить предыдущую транзакцию и открывать новую. При исчерпании сообщений, агент должен завершить свою работу.
Активация ASAP и Delayed агента
КМА, при получении очередного сообщения из очереди данных, должен активировать агента по соответствующей НЗ. Экземпляр агента получается однократно через фабрику и запомиается в MessageListener. При получении JMS-сообщения агенту передается Iterable<E> (для унификации API c агентами по-расписанию). Iterable фактически будет возвращать только одно сообщение за один запуск. Данный Iterable, в отличие от агентов по-расписанию, не опрашивает очередь, а содержит уже полученное КМА сообщение. Не следует создавать объект Iterable для каждого запуска агента. При интенсивном потоке сообщений нагрузка на GC будет избыточной. Можно держать по одному Iterable на каждую активную "настройку запуска".
Передача Iterable вместо одного конкретного сообщения обусловлено унификацией API КМА для агентов в режимах ASAP и по-расписанию, что позволяет менять режим работы агента настройками в МА, без модификации кода агента.
Политика наложения расписания
Для агента по-расписанию в МА задается "Политика наложения расписания". Она определяет, что делать, если предыдущий запуск агента по одной НЗ не завершился и поступил сигнал для очередного запуска агента. Возможные политики:
- наложить - разрешает запуск одновременно с предыдущим запуском. Дизайн агента должен предусматривать конкурентное исполнение в едином адресном пространстве.
- игнорировать - не запускать новый экземпляр агента
- прервать - прерывать исполнение предыдущего экземпляра агента и запустить новый экземпляр
Политика действует в рамках одного КМА (одного экземпляра приложения) и только на агент, запущенный по той же самой НЗ.
КМА, получив сигнал на запуск некоторой НЗ, должен определить, нет ли активного агента по той же самой НЗ. Если есть, то при политике "наложить" - просто запускаем ещё один экземпляр агента. При политике "игнорировать" - не запускаем.
При политике "прервать" - необходимо инициировать остановку исполнения агента. Механизм прерывания описан в разделе "Лимитирование длительности исполнения агента". КМА должен дождаться завершения агента и только после этого активировать новый экземпляр.
Применяется индивидуальная политика для каждого агента в группе.
Передача параметров агенту
КМА активирует агент вызовом функции, реализуемой агентом:
String agentRun(AgentContext<T> context, P parameters);
Параметры для агента КМА получает в сообщении на запуск агента в виде JSON. Чтобы сформировать java-объект, содержащий параметры, КМА через Reflection должен получить класс параметра и десериализовать JSON в объект данного класса.
Штатно должны поддерживаться следующие классы параметра:
- Все примитивные типы (String, Double, Long, Date и т.д.)
- Properties
- Отображение имени параметра на примитивное значение( Map<String, String>, Map<String, Integer> и т.д.)
Отображение имени параметра на список примитивных значений (Map <String, List<String>, Map <String, Set<String, Date> и т.д.)
При развитии системы, можно будет подержать произвольный тип параметров. Для этого КМА должен будет передать в МА схему класса параметра (сериализацией java-класса или JSON-Schema). В GUI МА динамически сформировать форму по схеме. В сигнале на запуск агента сформировать JSON с параметрами по схеме.