= Rest сервис Topic-management

=== Rest сервис для работы с топиками.

Сервис состоит из 2-х контекстов:

- _topic-management_ - управление топиками (создание/изменение/удаление)
- _topic-subscription_ - клиентский сервис подписки и публикации сообщения

При смене контекста в конфигурационных файлах требуется перераздать права на данный сервис.

Схема работы подписок

image::Topic_publish_diagram.png[]

Схема работы нотификаций

image::Scheme_of_work_notifications(events).png[]

=== Права

Право доступа в сервис _topic-management_

Для доступа к сервису _topic-management_ у аккаунта должно быть право доступа в данный сервис.

По умолчанию право на доступ к сервису есть у пользователя _admin_.

Используя данного пользователя в сервисе _permission-management_ можно раздать права на аккаунты систем.

Параметры для выдачи аккаунту прав на сервис:

- _objectId_: идентификатор учётной записи
- _objectType_: account
- _subjectId_: topic-management
- _subjectType_: service

Право manage сервиса _topic-subscription_

Для доступа к менеджерским функциям сервиса _topic-subscription_ у аккаунта должно быть право manage - данное право открывает доступ к управлению подписками.

Функционал управления подписками реализован в методах _subscribe_ и _unsubscribe_, пример использования приведён в подпунктах “Массовая обработка при наличии права manage для данного сервиса”.

Параметры для выдачи аккаунту права _manage_:

- _objectId_: идентификатор учётной записи
- _objectType_: account
- _subjectId_: topic-subscription
- _subjectType_: service
- _action_: manage

=== Конфигурационные файлы

Конфигурационные файлы по умолчанию хранятся  в папке сервера <karaf_home>\etc\

- _ru.entaxy.esb.system.event.handler.cfg_

[source,properties]
----
# максимальное колчичество попыток отправить сообщение в очередь
# почитать подробнее можно в https://camel.apache.org/components/latest/eips/dead-letter-channel.html#deadLetterChannel-Redelivery
redelivery.maximumRedeliveries=-1
# время между попытками отправить сообщение в очередь
redelivery.redeliveryDelay=5000

#cron - по умолчанию запускается каждые 00:00:00,
#cron выражение использует знак "+" как разделитель для модуля quartz2l
quirtz.job.clean.cron=0+0+0+*+*+?+*
----

- _ru.entaxy.esb.system.event.rest.cfg_

[source,properties]
----
service.host=http://0.0.0.0

service.port.management=9090
#Рутовый контекст управляющего сервиса, при изменении требуется перераздать права для данного сервиса
service.root.path.management=/topic-management

service.port.subscription=9092
#Рутовый контекст клиентского сервиса, при изменении требуется перераздать права для данного сервиса
service.root.path.subscription=/topic-subscription
----

=== Методы topic-management:

- _create_ - создать топик
При создании и изменении топика, есть возможность передать списки систем, которым доступна подписка или публикация в данном топике.

Запрос:

_POST server:9090/topic-management/create_

Тело
[source,json]
----
{
    "topicName": "boomNews",
    "possibleSubscribers" : ["systemUuid1", "systemUuid2"],
    "possiblePublishers" : ["systemUuid3", "systemUuid4"]
}
----

Ответ:

Статус 201

Тело
[source,json]
----
{
    "title": "Topic created",
    "topicName": "<topic_name>",
    "subscriberErrors": {
        "systemNotFound": [
            "<system_uuid>"
        ]
    },
    "publisherErrors": {
        "systemNotFound": []
    }
}
----

- update - изменить топик (название не меняется, так как на него завязана сама очередь в брокере)

Запрос:

_POST server:9090/topic-management/update_

Тело

[source,json]
----
{
    "topicName": "boomNews",
    "possibleSubscribers" : ["systemUuid1", "systemUuid2"],
    "possiblePublishers" : ["systemUuid3", "systemUuid4"]
}
----

Ответ:

Статус 201

Тело
[source,json]
----
{
    "title": "Topic updated",
    "topicName": "<topic_name>",
    "subscriberErrors": {
        "systemNotFound": [
            "<system_uuid>"
        ]
    },
    "publisherErrors": {
    "systemNotFound": []
    }
}
----

- delete - топик помечается как удалённый

Запрос:

_POST server:9090/topic-management/delete_

Тело
[source,json]
----
{
    "topicName": "boomNews"
}
----

Ответ:

Статус 200

Тело
[source,json]
----
{
    "title": "Topic deleted",
    "topicName": "<topic_name>"
}
----


- clean - очистка хранилища топиков

Окончательное удаление топиков, помеченных как удалённые.

Запрос:

_POST server:9090/topic-management/clean_

Ответ:

Статус 200

Тело
[source,json]
----
{
    "title": "Cleaned",
    "topicDeleted": "<count_of_deleted_topics>"
}
----


=== Методы topic-subscription:

- _subscribe_ - подписаться на топик

Запрос:

_POST server:9090/topic-subscription/subscribe_

Тело
[source,json]
----
{
    "topicName": "boomNews",
    "subscriptionType": "PUSH"
}
----

Ответ:

Статус 201

Тело
[source,json]
----
{
    "title": "Subscription created",
    "topicName": "<topic_name>",
    "systemName": "<system_name>",
    "subscriptionType": "<subscription_type>"
}
----

=== 		Массовая обработка при наличии права manage для данного сервиса.

- _subscribe_ - подписаться на топик

Запрос:

_POST server:9092/topic-subscription/subscribe_

Тело
[source,json]
----
{
    "topicName": "boomNews",
    "systemUuids": [
        {
            "systemUuid": "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
            "subscriptionType": "PUSH"
        },
        {
            "systemUuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "subscriptionType": "PULL"
        },
        {
            "systemUuid": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
            "subscriptionType": "PULL"
        }
    ]
}
----

Ответ:

Статус 200

Тело
[source,json]
----
[
    {
        "title": "Internal Server Error",
        "detail": "System not found NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
        "reason": "javax.persistence.NoResultException: No entity found for query"
    },
    {
        "title": "Subscription created",
        "topicName": "ooooo111-ff6e-4219-a878-bff120c495f1",
        "systemUUID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "subscriptionType": "PULL"
    },
    {
        "title": "Forbidden",
        "detail": "No permission to subscribe for system YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
    }
]
----


- _unsubscribe_ - отписаться от топика

Запрос:

_POST server:9090/topic-subscription/unsubscribe_

Тело
[source,json]
----
{
    "topicName": "boomNews"
}
----

Ответ:

Статус 200

Тело
[source,json]
----
{
    "title": "Subscription deleted",
    "topicName": "<topic_name>",
    "systemName": "<system_name>"
}
----

===	Массовая обработка при наличии права manage для данного сервиса.

Запрос:

_POST server:9092/topic-subscription/unsubscribe_

Тело
[source,json]
----
{
    "topicName": "boomNews",
    "systemUuids": [
        {
            "systemUuid":"NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
            "subscriptionType": "PUSH"
        },
        {
            "systemUuid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
            "subscriptionType": "PULL"
        },
        {
            "systemUuid": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
            "subscriptionType": "PULL"
        }
    ]
}
----

Ответ:

Статус 200

Тело
[source,json]
----
[
    {
        "title": "Internal Server Error",
        "detail": "System not found NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN",
        "reason": "javax.persistence.NoResultException: No entity found for query"
    },
    {
        "title": "Subscription deleted",
        "topicName": "ooooo111-ff6e-4219-a878-bff120c495f1",
        "systemUUID": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
    },
    {
        "title": "Subscription not found",
        "topicName": "ooooo111-ff6e-4219-a878-bff120c495f1",
        "systemUUID": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY"
    }
]
----

- _publish_ - опубликовать событие

Запрос:

_POST server:9090/topic-subscription/publish_

Тело
[source,json]
----
{
    "topicName": "boomNews", "message": "messageText2"
}
----

		Ответ:
			Статус 200
			Тело
				{
					"title": "Message published",
					"topicName": "<topic_name>"
				}

=== Примеры ответов при ошибке

- _Передан некорректный JSON или логин не определён_

Статус 400

Тело
[source,json]
----
{
    "title": "Incorrect input parameters",
    "detail": "Cannot parse incoming JSON or login/system not defined"
}
----

- _Подписка не найдена_

Статус 404

Тело
[source,json]
----
{
    "title": "Subscription not found",
    "topicName": "${exchangeProperty.topicName}",
    "systemUUID": "${header.X-SystemUuid}"
}
----

- _Топик не зарегистрирован_

Статус 400

Тело
[source,json]
----
{
    "title": "Topic not registered",
    "topicName": "${exchangeProperty.topicName}"
}
----

- _Система не найдена_

Статус 500

Тело
[source,json]
----
{
    "title": "Internal Server Error",
    "detail": "System not found ${header.X-SystemUuid}",
    "reason": "${exception.stacktrace}"
}
----

- _Неизвестный тип подписки_

Статус 500

Тело
[source,json]
----
{
    "title": "Internal Server Error",
    "detail": "Unknown subscription type ${exchangeProperty.subscriptionType}",
    "reason": "${exception.stacktrace}"
}
----

- _Нет прав на выполнение запрошенной операции_

Статус 403

Тело
[source,json]
----
{
    "title": "Forbidden",
    "detail": "No permission to subscribe"
}
----

- _Неизвестная ошибка_

Статус 500

Тело
[source,json]
----
{
    "title": "Internal Server Error",
    "detail": "Unknown exception",
    "reason": "${exception.stacktrace}"
}
----

== Работа модуля топиков в кластере

=== Подписки

*PULL подписки не реализованы!*

Для реализации подписки используются _durable shareable_ подписчики _Apache Artemis_ топиков, а для доставки сообщений из топиков создаются специальные маршруты отправляющие сообщения в подписанную систему(PUSH-подписка). При работе в кластере системой создаются дублирующие маршруты на каждом узле, которые работают в конкурентном режиме, т.е. при падении одного из узлов доставка сообщений будет производиться оставшимися узлами.

Работа в кластере обеспечивается через компоненты _Apache Felix Event Admin_, реализующий рассылку служебных событий по топикам внутри узла,
и _Apache Karaf Cellar_ с интеграцией с _Event Admin_, реализующий рассылку данного события по всем узлам кластера. При создании/удалении подписки система создаёт соответствующее событие и отправляет в специальный топик _subscription_, далее подписчики на всех узлах, входящих в кластер, получают и обрабатывают данное событие.

=== Настройки

Настройки производятся на любом узле либо на мастер узле, в зависимости от настроек кластера.

Для синхронизации топика _subscription_ в конфигурационном файле  _org.apache.karaf.cellar.groups.cfg_, нужно добавить строки:

[source,properties]
----
    default.event.blacklist.inbound = none
    default.event.blacklist.outbound = none
    default.event.whitelist.inbound = subscription
    default.event.whitelist.outbound = subscription
----

Далее если _Apache Karaf Cellar_ настроен правильно, то конфигурационные файлы _org.apache.karaf.cellar.groups.cfg_ должны синхронизироваться и строки, представленные выше, появятся на всех узлах.

Если синхронизация конфигурационных файлов не проходит, можно проверить статус _cellar_ продюсера командой:

    cluster:producer-status

если выключен, то можно включить командой:

    cluster:producer-start

=== Удаление топиков

Удаление топиков происходит в 2 этапа

- При вызове метода _delete_ топик помечается в БД, как удалённый

- По расписанию запускается задача (по умолчанию раз в сутки в 00:00), которая удаляет топик окончательно вместе с подписками, правами и консьюмерами.
Либо очистка запускается вручную вызовом метода _clean_ сервиса _topic-management_

Если до очистки системы изменить топик помеченный как удалённый или попытаться создать его заново, то восстановится старый топик

=== Плановая задача по очистке топиков

Т.к. удаление топиков происходит в 2 этапа, с помощью планировщика _Quartz2_, работающего в кластере, запускается задача по очистке. Запуск происходит в конкурентном режиме, узел первый запустивший задачу получает приоритет, на остальных узлах задача останавливается до следующего запуска.

Частота запуска задачи настраивается в конфигурационном файле  _ru.entaxy.esb.system.event.handler.cfg_ в свойстве quirtz.job.clean.cron=0+0+0+*+*+?+*
с помощью cron выражения. Особенность cron выражения для quartz в том что “++” используется, как разделитель.

