Способы интеграции
==================

Существует два основных подхода для интеграции с Synergy:

* Прямая интеграция — интеграционные модули разрабатываются
  с использованием API Synergy и интегрируемых систем.
  Синхронизация данных между системами и координация обмена между ними
  остаётся за разработчиком интеграционного модуля

* Событийная интеграция — когда какая-либо из подсистем Synergy
  генерирует различные события, связанные с какими-либо данными.
  Обработчики этих событий (на стороне Synergy) при необходимости
  преобразовывают данные событий и передают их интегрируемой системе
  через какой-либо транспортный уровень

Прямая интеграция
-----------------

ARTA Synergy предоставляет API для доступа к своим функциям
с помощью rest сервисов. Описание методов REST API можно
найти в `данном разделе`_.
Авторизация для всех методов API — ``Basic HTTP``.

Событийная интеграция
---------------------

Под «событием» мы будем подразумевать сообщение о каком-либо изменении
в ARTA Synergy, содержащее тип события
и минимально необходимые для получения связанной с событием информации
либо воздействия на Synergy данные. Обработчик события (или событий) —
программный модуль, читающий сообщения о событиях из ``JMS Queue`` или ``JMS Topic``
и осуществляющий, при необходимости, доступ к экземпляру Synergy,
сгенерировавшему сообщение, с помощью API Synergy.

Обработчик событий является отдельным от ARTA Synergy приложением,
которое может работать как на том же сервере приложений,
что и ARTA Synergy, так и на удалённом.

Кроме этого, обработчик события может иметь собственные конфигурационные файлы,
необходимые для реализации целевого назначения.

Обработчик событий может обрабатывать как конкретное событие
(например, ``event.registers.formdata.add``),
так и класс событий (например, ``event.registers.*``).

Обработка события может происходить в 3 этапа:

1. Получение события
2. Получение и преобразование необходимых обработчику данных
3. Передача сформированного пакета данных далее (опционально)

ARTA Synergy генерирует событие в случае,
если для этого события настроены обработчики.
Обработчики событий настраиваются в конфигурационном файле
``${jboss.home}/standalone/configuration/arta/api-observation-configuration.xml``.

Сообщение, помещаемое в очередь ``JMS``,
представляет собой экземпляр ``javax.jms.TextMessage``.
Тело сообщения зависит от типа события,
его описание можно посмотреть ниже среди описаний типов событий.
Каждое событие содержит свойство ``api_event``, указывающее на тип события,
вызвавшего его (содержимое тега ``<event>event.registers.formdata.add</event>`` в конфигурационном файле).

Например:

.. code-block:: xml

    <configuration>
        <listener>
            <queue>java:jboss/queues/Synergy/UsersQueue</queue>
            <event>event.users.*</event>
        </listener>
        <listener>
            <queue>java:jboss/queues/Synergy/RegisterCreateDocQueue</queue>
            <event>event.registers.formdata.add</event>
        </listener>
    </configuration>

В этом примере настроены обработчики:

1. ``java:jboss/queues/Synergy/UsersQueue`` для всех событий класса
   ``event.users.*``, т.е. всех событий, связанных с пользователями:
   ``event.users.account.change``,
   ``event.users.formdata.change``,
   ``event.users.account.add`` и т.д.
2. ``java:jboss/queues/Synergy/RegisterCreateDocQueue``
   для события добавления записи реестра ``event.registers.formdata.add``.

Рассмотрим, например, код обработчика очереди ``UsersQueue``:

.. code-block:: java

    public class UsersMessagesListener implements MessageListener {

        public void onMessage(Message message) {
            //Получаем идентификатор пользователя, для которого
            //сгенерировано событие
            String userID = ((TextMessage) message).getText();
            //Получаем тип события
            String eventType = message.getStringProperty("api_event");

            //Выполнение действия по получению дополнительных данных через API
            //и прочих операций, зависящих от условий решаемой задачи
        }
    }

Ниже описаны типы событий, которые могут быть сгенерированы ARTA Synergy.

Для события ``[event.orgstructure.department.formdata.change]`` -
идентификатор подразделения, для события
``[event.orgstructure.position.formdata.change]`` - идентификатор
должности, для события ``[event.users.formdata.change]`` - идентификатор
пользователя будет передаваться как основной параметр, остальные как
свойства. Получить их можно следующим образом:

.. code-block:: java

    public void onMessage(Message message) {
        //Получение идентификатора пользователя/должности/подразделения (В зависимости от события на которое подписаны)
        String userID = ((TextMessage) message).getText();
        //Получаем идентификатор формы
        String formUUID = message.getStringProperty("formUUID");
    }

.. toctree::
  :maxdepth: 2
  :numbered:

  integration/events

Блокирующий процесс
-------------------

Блокирующий процесс предназначен для того,
чтобы предоставить возможность
в маршрут активации/изменения/удаления реестра
вставить асинхронный вызов внешнего модуля.
Основное отличие блокирующего процесса от `события реестра`_
заключается в том, что:

* при использовании блокирующего процесса маршрут реестра
  дожидается ответа о результате выполнения операции внешним модулем
* блокирующий процесс может завершиться положительно или отрицательно,
  что повлияет на дальнейшую работу маршрута
  (Если блокирующий процесс завершится отрицательно —
  процесс остановится, если положительно — то продолжит работу дальше)

Модуль, реализующий блокирующий процесс, должен представлять собой отдельное приложение,
задеплоенное на jboss в соответствии с правилами,
описанными в разделе `Как задеплоить интеграционное приложение`_.

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

Конфигурация блокирующего процесса
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Для того, чтобы добавить блокирующий процесс,
необходимо выполнить следующие действия:

1. Добавить процесс с в маршрут реестра в конфигураторе:

      .. figure:: _static/img/integration/blocking-add.png
         :scale: 68%

   Название процесса должно начинаться с ``event.blocking.``
   и далее строка, характеризующая суть блокирующего процесса.

2. Создать очередь JMS для блокирующего процесса.
   Для этого необходимо в конфигурационный файл
   (в стандартной установке это ``/opt/synergy/jboss/standalone/configuration/standalone-onesynergy.xml``)
   в секцию ``<subsystem xmlns="urn:jboss:domain:messaging:1.2">`` добавить:

   .. code-block:: xml

      <jms-queue name="ExampleQueue">
          <entry name="java:jboss/queues/Integration/ExampleQueue"/>
          <durable>true</durable>
      </jms-queue>


3. Связать очередь и процесс через конфигурационный файл
   ``{$jboss.home}/standalone/configuration/arta/api-observation-configuration.xml``,
   добавив в него следующее:

   .. code-block:: xml

      <listener>
           <queue>java:jboss/queues/Integration/ExampleQueue</queue>
           <event>event.blocking.example</event>
      </listener>

Обратите внимание, что название блокирующего процесса,
указанное в маршруте в конфигураторе должно быть
равно значению тега в конфигурационном файле
``api-observation-configuration.xml`` (в данном примере: ``event.blocking.example``)
и название очереди должно совпадать со значением тега
``queue`` конфигурационного файла
``api-observation-configuration.xml`` (в данном примере: ``java:jboss/queues/Integration/ExampleQueue``)

Сообщение, передаваемое в очередь, является экземпляром ``TextMessage``.
Содержимым сообщения является объект JSON с полями:

    1. dataUUID — идентификатор данных по форме записи реестра
    2. executionID — идентификатор блокирующего процесса
    3. documentID — идентификатор документа реестра

После того, как модуль обратится к внешней системе
и выполнит необходимые действия, он должен вызвать метод API Synergy
для того, чтобы возвратить результат выполнения процесса и продолжить работу маршрута.
Для того, чтобы это сделать, необходимо вызвать метод API
``rest/api/processes/signal``.

.. note:: Сигнал блокирующему процессу для его разблокировки/блокировки
          нужно отправлять после того, как этот процесс был запущен, то есть после того как
          транзакция с запуском процесса была завершена. Для этого, перед отправкой сигнала,
          проверяйте на наличие такого процесса в БД. В противном случае, блокирующий процесс
          может завершиться в транзакции, но в маршруте нет.

В примере кода ниже разблокировка маршрута осуществляется в методе ``onMessage``.
Если время выполнения действия значительно или зависит от внешних факторов
(например, доступность интегрируемой системы,
или необходимость ввода пользователем данных в интегрируемой системе),
то разблокировка маршрута может произойти позже,
в любой другой момент времени из другого метода,
а сам метод onMessage должен завершиться без ошибок, «запомнив» переданные параметры.

.. code-block:: java

    package kz.arta.synergy.samples.processes.blocking;

    import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
    import org.codehaus.jackson.JsonFactory;
    import org.codehaus.jackson.JsonParser;
    import org.codehaus.jackson.JsonToken;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    import javax.jms.Message;
    import javax.jms.MessageListener;
    import javax.jms.TextMessage;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.net.HttpURLConnection;
    import java.net.URL;

    /**
     * <p><Пример блокирующего процесса</p>
     */
    public class BlockingQueueListener implements MessageListener {

        public void onMessage(Message message) {

            String dataUUID = null;
            String executionID = null;
            String documentID = null;

            if (!(message instanceof TextMessage)){
                return;
            }

            try {

                JsonFactory factory = new JsonFactory();
                JsonParser parser = factory.createJsonParser(((TextMessage) message).getText());
                JsonToken token = null;

                while ((token = parser.nextToken()) != null) {
                    if (token == JsonToken.FIELD_NAME) {
                        String fieldName = parser.getText();
                        parser.nextToken();
                        String value = parser.getText();
                        if (fieldName.equals("dataUUID")){
                            dataUUID = value;
                        } else if (fieldName.equals("executionID")){
                            executionID = value;
                        } else if (fieldName.equals("documentID")){
                            documentID = value;
                        }
                    }
                }

             //Выполнение каких-либо действий
             ….......

                //Разблокировка маршрута

                String address = "http://127.0.0.1:8080/Synergy";
                String login = "1";
                String password = "1";
                String signal = "got_agree";
                boolean isSuccess = false;

                try {
                    URL url = new URL(address + "/rest/api/processes/signal?signal=" + signal + "&executionID=" + executionID + "&param1=resolution&value1=signal_is_" + signal);

                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.setRequestProperty("Accept", "application/json; charset=utf-8");

                    String encoded = Base64.encode((login + ":" + password).getBytes());
                    conn.setRequestProperty("Authorization", "Basic " + encoded);

                    String output;
                    StringBuffer result = new StringBuffer();

                    BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

                    while ((output = br.readLine()) != null) {
                        result.append(output);
                    }

                    conn.disconnect();

                    JsonFactory factory = new JsonFactory();
                    JsonParser parser = factory.createJsonParser(result.toString());
                    JsonToken token = null;

                    while ((token = parser.nextToken()) != null) {
                        if (token == JsonToken.FIELD_NAME) {
                            String fieldName = parser.getText();
                            token = parser.nextToken();
                            if (fieldName.equals("errorCode") && parser.getText().equals("0")){
                                isSuccess = true;
                            }
                        }
                    }
                } catch (Exception exc){
                    logger.error(exc.getMessage(), exc);
                }

            } catch (Exception exc){
                logger.error(exc.getMessage(), exc);
            }
        }
    }


Способы авторизации в ARTA Synergy
----------------------------------

REST API ARTA Synergy доступно только авторизованным пользователям.
Тип авторизации — ``BASIC HTTP``.
Методы API выполняются от имени того пользователя,
который авторизован. Имеются следующие типы авторизации:

Авторизация по логину и паролю
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Авторизация пользователя по его логину и паролю приемлема в тех случаях,
когда приложение может знать текущий логин и пароль пользователя, например:

* Приложение предоставляет альтернативный интерфейс
  к некоторым модулям Synergy
  (мобильное приложение, десктопный клиент для хранилища)
* Приложение представляет собой `server-side` утилиту для синхронизации,
  для которого создан выделенный пользователь,
  и его логин и пароль хранятся в конфигурационном файле на сервере.

Для реализации данного типа авторизации надо передать в запросе заголовок
``Authorization`` со значением:

    ``"Basic " + Base64("login" + ":" + "password")``

Например:

+--------------------+--------------------------------------+
| Логин              | `Administrator`                      |
+--------------------+--------------------------------------+
| Пароль             | `123456`                             |
+--------------------+--------------------------------------+
| Значение заголовка | `Basic QWRtaW5pc3RyYXRvcjoxMjM0NTY=` |
+--------------------+--------------------------------------+

.. code-block::
    :emphasize-lines: 3

    POST /Synergy/rest/api/report/do?reportID=daa64ab2-1a79-4ea2-9c24-372e35d9da3e&amp;fileName=report.pdf HTTP/1.1
    Host: demo.arta.kz
    Authorization: Basic QWRtaW5pc3RyYXRvcjoxMjM0NTY=
    Cache-Control: no-cache
    Content-Type: multipart/form-data

Пример кода на JAVA:

.. code-block:: java
    :emphasize-lines: 5

    String login = "Administrator";
    String password = "123456";

    HttpResponse<String> response = Unirest.get("http://demo.arta.kz/Synergy/rest/api/admin/db/current_version")
    .header("authorization", Base64.getEncoder().encodeToString((login + ":" + password).getBytes(‌"UTF‌​-8"​)))
    .asString();

Пример кода на JavaScript (jQuery):

.. code-block:: js
    :emphasize-lines: 10

    var login = "Administrator";
    var password = "123456";

    var settings = {
      "async": true,
      "crossDomain": true,
      "url": "http://demo.arta.kz/Synergy/rest/api/admin/db/current_version",
      "method": "GET",
      "headers": {
        "authorization": ("Basic " + btoa(login + ":" + password))
      }
    }

    $.ajax(settings).done(function (response) {
      console.log(response);
    });

Пример кода на PHP:

.. code-block:: php
    :emphasize-lines: 11

    <?php

    $login = "Administrator";
    $password = "123456"

    $request = new HttpRequest();
    $request->setUrl('http://demo.arta.kz/Synergy/rest/api/admin/db/current_version');
    $request->setMethod(HTTP_METH_GET);

    $request->setHeaders(array(
      'authorization' => "Basic " . base64_encode("$login:$password")
    ));

    try {
      $response = $request->send();

      echo $response->getBody();
    } catch (HttpException $ex) {
      echo $ex;
    }

Сессионная авторизация
~~~~~~~~~~~~~~~~~~~~~~

Сессионная авторизации используется для встроенных WEB-модулей.
При cессионной авторизации также используется тип — ``BASIC HTTP``,
но в качестве логина пользователя необходимо использовать
значение ``$session`` и в качестве пароля —
полученное значение ``sso_hash``.

Таким образом заголовок ``Authorization`` должен иметь значение:

    ``"Basic " + Base64("$session" + ":" + "sso_hash")``

Например:

+-----------------------+--------------------------------------------------------+
| Значение ``sso_hash`` | ``D3RONfC52dtJO5XgDyn5qUMv``                           |
+-----------------------+--------------------------------------------------------+
| Значение заголовка    | ``Basic JHNlc3Npb246RDNST05mQzUyZHRKTzVYZ0R5bjVxVU12`` |
+-----------------------+--------------------------------------------------------+

Кроме того, получить параметры авторизации можно с помощью переменной
окружения основного WEB-приложения Synergy:

* ``$CURRENT_USER`` - представляет собой JSON-объект следующего вида:

.. code-block:: json

    {
      "id": "Идентификатор текущего пользователя",
      "sso_hash": "hash-сумма для идентификации пользователя",
      "surname": "Фамилия текущего пользователя",
      "name": "Имя текущего пользователя",
      "patronymic": "Отчество текущего пользователя"
    }


Авторизация по ключам
~~~~~~~~~~~~~~~~~~~~~

Модуль, который хочет авторизоваться от имени какого-либо пользователя таким способом,
должен сгенерировать для него ключевую пару,
обеспечив сохранность закрытого ключа.
Затем модуль сохраняет получивший открытый ключ для пользователя в Synergy,
используя следующий вызов API:

    ``rest/api/person/generate_auth_key``

Этот вызов назначает ключ тому пользователю, от имени которого выполняется.

Параметр ``user_token_expire_interval`` регулирует интервал устаревания ключей авторизации.
Пример настройки интервала:

.. code-block:: sql

	   insert into options (id, value) values ('user_token_expire_interval', '5256000'); -- 10 лет

.. note:: Интервал устаревания ключа указывается в минутах. Значение по умолчанию ``0``, то есть если ранее для
          данного пользователя был сгенерирован другой ключ, то предыдущий автоматически становится недействительным.

Создать ключ можно только для существующего WEB-модуля,
так как для этого требуется идентификатор приложения.
Если у вас нет необходимости разрабатывать WEB модуль,
но есть необходимость в использовании авторизации по ключам,
можно создать такой модуль на уровне БД и отключить его использование
в административном приложении SynergyAdmin для всех элементов оргструктуры.

Использование этого ключа для авторизации аналогично использованию сессионного ключа.
Тип авторизации ``Basic HTTP``,
в качестве логина пользователя надо использовать строку «``$key``»,
в качестве пароля — полученный с помощью API ключ.

Таким образом заголовок ``Authorization`` должен иметь значение:

    ``"Basic " + Base64("$key" + ":" + "значение_ключа")``

Например:

+---------------------+------------------------------------------------------------------------------------------------------------------------+
| Значение ключа      | ``MS03Y2Q0ZGU3YS0zYjRkLTQ2NjgtYWIyOC0zZDI1YzgxZGNmOGZfMjAxMy0xMC0zMSAxNzo0Mg==``                                       |
+---------------------+------------------------------------------------------------------------------------------------------------------------+
| Значение заголовка  | ``Basic JGtleTpNUzAzWTJRMFpHVTNZUzB6WWpSa0xUUTJOamd0WVdJeU9DMHpaREkxWXpneFpHTm1PR1pmTWpBeE15MHhNQzB6TVNBeE56bzBNZz09`` |
+---------------------+------------------------------------------------------------------------------------------------------------------------+
