1.3. Установка и применение JavaScript интерпретатора

1.3.1. Введение

JavaScript интерпретатор (далее просто «интерпретатор») - внешний модуль, предоставляющий возможность написать server-side скрипт на JavaScript с использованием объектов платформы. На данный момент использовать интерпретатор можно для обработки в блокирующем процессе. Поддерживаемые объекты: формы, личные карточки. С помощью интерпретатора есть возможность решать такие задачи, как арифметические действия с числовыми полями, с датами, производить необходимые расчеты в динамических таблиц и т.д.

Данная документация актуальна для версии 2.63 - 3.15.

1.3.2. Автоматический способ установки

В данном разделе описывается автоматический способ установки интерпретатора используя средства управления программными пакетами операционных систем, базирующихся на GNU/Debian Linux. В случае возникновения проблем во время установки пакета Вы можете воспользоваться устаревшим ручным способом.

  1. Установите пакет arta-synergy-interpreter:

    aptitude install arta-synergy-interpreter

В процессе установки будут запрошены:

  • пароль для доступа к MySQL, т.к. установка требует модификации этой базы данных;

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

  1. После установки и перезагрузите JBoss.

Интерпретатор будет доступен по адресу:

http://адрес_сервера:http_порт_сервера/interpreter/

1.3.3. Ручной способ установки

  1. Создать базу данных mysql (с кодировкой UTF-8):

    mysql -uroot -proot
    CREATE DATABASE interpreter CHARACTER SET utf8 ;
    use interpreter
    
  2. Выполнить в базе данных скрипт:

    create table INTERPRETER_PROCESS (ID BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(512),
    DESCRIPTION VARCHAR(2048),
    CODE VARCHAR(10000),
    LOGIN VARCHAR(512),
    PASSWORD VARCHAR(512),
    AUTH_KEY VARCHAR(2048),
    DEFAULT_MESSAGE VARCHAR(512),
    PRIMARY KEY(ID));
    
  3. Настроить на нее datasource сервера приложений с JNDI-именем java:jboss/datasources/int-ds:

    nano /opt/synergy/jboss/standalone/configuration/standalone-onesynergy.xml

    В секцию <subsystem xmlns="urn:jboss:domain:datasources:1.1"> подсекции <datasources> добавить:

    <xa-datasource jndi-name="java:jboss/datasources/int-ds" pool-name="interpreter" enabled="true" use-ccm="false">
                <xa-datasource-property name="URL">
                jdbc:mysql://127.0.0.1:3306/interpreter?useUnicode=true&amp;characterEncoding=utf8
                </xa-datasource-property>
                <driver>com.mysql</driver>
                <xa-pool>
                    <min-pool-size>20</min-pool-size>
                    <max-pool-size>200</max-pool-size>
                    <is-same-rm-override>false</is-same-rm-override>
                    <interleaving>false</interleaving>
                    <pad-xid>false</pad-xid>
                    <wrap-xa-resource>false</wrap-xa-resource>
                </xa-pool>
                <security>
                    <user-name>root</user-name>
                    <password>root</password>
                </security>
                <validation>
                <validate-on-match>false</validate-on-match>
                    <background-validation>false</background-validation>
                </validation>
                <statement>
                    <share-prepared-statements>false</share-prepared-statements>
                </statement>
    </xa-datasource>
    
  4. Создать на сервере приложений очередь с JNDI-именем java:jboss/queues/Integration/InterpreterQueue:

    nano /opt/synergy/jboss/standalone/configuration/standalone-onesynergy.xml

    В секцию <jms-destinations> добавить:

    <jms-queue name="InterpreterQueue">
        <entry name="java:jboss/queues/Integration/InterpreterQueue"/>
        <durable>true</durable>
    </jms-queue>
    
  5. Связать очередь и процесс через конфигурационный файл

    nano /opt/synergy/jboss/standalone/configuration/arta/api-observation-configuration.xml

    Примечание

    Если файл отсутствует, то необходимо его создать и добавить строки <listeners> </listeners>. Между ними нужно разместить очередь универсального слушателя очередей, указанную ниже. Также необходимо проверить права на файл (должно быть jboss:synergy).

    <listener>
        <queue>java:jboss/queues/Integration/InterpreterQueue</queue>
        <event>event.blocking.interpreter.*</event>
    </listener>
    
  6. Установить на сервер ear-файл и скопировать его в директорию /opt/synergy/jboss/standalone/deployments:

    cd  /opt/synergy/jboss/standalone/deployments
    cp -r  /путь до файла/synergy-interpreter-ear1.8.ear ./
    chown -R jboss:synergy synergy-interpreter-ear1.8.ear
    
  7. Перезапустите сервер.

1.3.3.1. После установки

  1. Создать файл interpreter.properties:

    nano /opt/synergy/jboss/standalone/configuration/arta/interpreter/interpreter.properties

    Примечание

    Если каталога interpreter не существует, необходимо создать его с помощью команды mkdir.

    Настроить права для данного файла:

    сhown -R jboss:synergy interpreter.properties

    Добавить в него строки:

    synergy.address=http://localhost:8080/Synergy
    interpreter.prefix=event.blocking.interpreter.
    script.not.found.send.ok=false
    script.not.found.default.msg=Скрипт не найден
    script.not.found.default.login=1
    script.not.found.default.pwd=1
    
  2. Включить режим логирования TRACE. Для этого откройте файл конфигурации

    nano /opt/synergy/jboss/standalone/configuration/standalone-onesynergy.xml

    В секцию <profile> подсекции <subsystem xmlns="urn:jboss:domain:logging:1.1"> добавьте:

    <periodic-rotating-file-handler name="iterpreter-handler">
        <formatter>
            <pattern-formatter pattern="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
        </formatter>
        <file relative-to="jboss.server.log.dir" path="interpreter.log"/>
            <suffix value=".yyyy-MM-dd"/>
        <append value="true"/>
    </periodic-rotating-file-handler>
    

    А также:

    <logger category="kz.arta.ext.interpreter">
        <level name="TRACE"/>
            <handlers>
                <handler name="iterpreter-handler"/>
            </handlers>
    </logger>
    
  3. Перезапустите сервер.

1.3.4. Защита

Модуль «Интерпретатор» не имеет встроенной защиты от несанкционированного входа - защитить его можно внешним образом, например, используя nginx.

Ниже приведём пример с установкой защиты от входа в модуль при помощи web-сервера nginx, его модулей http_auth_request_module, headers-more-nginx-module и метода REST API Synergy rest/api/auth/{role}. Будем предполагать, что используется стандартный конфигурационный файл для nginx, поставляемый вместе с Synergy, synergy-base.

1.3.4.1. Вводная часть

Веб-сервер nginx встроенными средствами позволяет ограничивать доступ к серверу или какому-либо location-у с проверкой имени пользователя и пароля по протоколу «HTTP Basic Authentication», однако стандартный модуль ngx_http_auth_basic_module позволяет задать только статические пары логин:пароль в парольном файле. Мы же хотим использовать данные учётных записей Synergy и в этом нам поможет модуль ngx_http_auth_request_module. Этот модуль ограничивает доступ путём выполнения подзапроса со всеми заголовками оригинального запроса. Если кодом ответа на подзапрос будет 2xx, то аутентификация будет считаться пройденной, в случае, если подзапрос возвращает 401-й код ошибки, в ответ на оригинальный запрос будет передан заголовок WWW-Authenticate из подзапроса.

Специально для подобных случаев в Synergy предусмотрен метод API rest/api/auth/{role}, где вместо {role} можно передать user, administrator или methodologist. В случае, если пользовательские данные авторизации, переданные в заголовке Authorization, соответствуют пользователю, который

  1. имеет доступ в систему и

  2. обладает указанной ролью,

метод вернёт код 200, в обратном случае - 403, а при отсутствии заголовка Authorization - 401.

Модуль headers-more-nginx-module понадобится нам для того, чтобы заменить содержимое заголовка WWW-Authenticate, которое передаёт API Synergy - в целях упрощения интеграции внешнего проигрывателя там сейчас передаётся None вместо Basic, а стандартная директива nginx, add_header, не срабатывает при 401 коде ответа от прокси.

1.3.4.2. Настройка

Для начала необходимо установить пакет nginx-extras. Возможен конфликт с пакетом nginx-full (если он у вас установлен) - в этом случае смело заменяйте последний на nginx-extras - он содержит всё то же самое, что и nginx-full + дополнительные модули.

# aptitude install nginx-extras

После установки вам необходимо добавить в конфигурационный файл synergy-base следующие директивы:

# editor /etc/nginx/sites-enabled/synergy-base

[ ... ]

server {
    server_name synergy.arta.pro; #DO NOT CHANGE. use dpkg-reconfigure arta-synergy-synergy
    
    [ ... ]

    # Новый location, используемый для аутентификации
    location = /auth-sd {
        proxy_pass              http://127.0.0.1:8080/Synergy/rest/api/auth/methodologist;
        more_set_headers -s 401 'WWW-Authenticate: Basic';
        proxy_pass_request_body off;
        proxy_set_header        Content-Length "";
        proxy_set_header        X-Original-URI $request_uri;
    }

    # И в секцию, которая соответствует Интерпретатору
    location /interpreter {
        auth_request            /auth-sd;
        proxy_pass              http://127.0.0.1:8080/interpreter;
         
        [ ... ]

    }

    [ ... ]

}

На этом настройка закончена, перезагрузим конфигурацию nginx:

# /etc/init.d/nginx reload

Теперь для доступа к /interpreter необходимо ввести логин и пароль активной учётной записи Synergy с правами «Synergy Developer» («суперметодолог»).

1.3.5. Интерфейс модуля

При переходе к модулю «Интерпретатор» открывается следующее окно:

Рисунок 1.12. Интерфейс модуля

Интерфейс модуля

В данном окне отображается список скриптов, которые можно редактировать и удалять, нажав соответствующую кнопку. Чтобы вставить новый скрипт, необходимо нажать кнопку «Добавить». Открывается окно:

Рисунок 1.13. Интерфейс модуля

Интерфейс модуля

Данное окно делится на две области: метаданные и код. Метаданные:

  • Название — название скрипта в формате event.blocking.interpreter.%название_скрипта%;

  • Описание;

  • Комментарий по умолчанию;

  • Авторизация.

В окне кода прописывается сам скрипт.

Скрипт может обращаться к параметрам авторизации, которые указаны в интерпретаторе, с помощью строковых переменных login, password и key.

Не забудьте сохранить написанный скрипт!

1.3.6. Запуск скрипта

Написанный скрипт можно запустить непосредственно из интерпретатора с помощью кнопки «Запустить» или нажатием клавиш Ctrl+S. При первом запуске скрипта открывается окно «Конфигурация выполнения скрипта», где можно ввести значения параметров dataUUID, documentID и executionID - эти параметры передаются скрипту интерпретатора при его запуске в блокирующем процессе Synergy:

Рисунок 1.14. Конфигурация выполнения скрипта

Конфигурация выполнения скрипта

Примечание:

Формально эти параметры не обязательны для запуска скрипта, но выполнение скрипта без них может приводить к ошибкам.

Если флаг «Открывать конфиг при запуске» установлен, то это окно будет отображаться при каждом запуске скрипта, иначе - по нажатию на кнопку .

Результат выполнения скрипта отображается в виде всплывающего сообщения в нижней правой части экрана:

Рисунок 1.15. Успешное завершение выполнения скрипта

Успешное завершение выполнения скрипта

Если при выполнении возникли ошибки, то они также отображаются во всплывающем сообщении:

Рисунок 1.16. Ошибка при выполнении скрипта

Ошибка при выполнении скрипта

1.3.7. Объекты ARTA Synergy

Встроенный в java интерпретатор позволяет передавать Java объекты JavaScript-у, поэтому модуль интерпретатор предоставляет пользователю:

  1. Объект платформы с названием platform или synergy;

  2. Все строки, пришедшие как входные данные в блокирующий процесс;

  3. documentData — объект FormData с подгруженными данными процесса.

Таким образом, код для суммирования двух полей формы и записи значения в третье поле документа будет выглядеть так:

summa = documentData.getValue(«a») + documentData.getValue(«b»);
documentData.setValue(«c», summa);
documentData.save();

Запись одного поля документа в поле личной карточки, при условии, что на форме есть Объект Synergy:

var cardData = synergy.getCardsManager().getUserCard(«идентификатор_формы»,
documentData.getValue(«user_chooser_id»));
cardData.setValue(«field», documentData.getValue(«document_field»));
cardData.save();

Пример работы с динамической таблицей:

for (i = 0; i < getRowsCount("id_таблицы"); i ++){
sum = sum + getValue("id_таблицы", "id_компонента", i);
}

1.3.7.1. Платформа

Назначение: Отвечает за создание экземпляров других объектов. Класс: kz.arta.ext.interpreter.platform.Platform.

Методы:

  • getFormsManager() возвращает «Менеджер данных по формам»;

  • getCardsManager() возвращает «Менеджер личных карточек».

1.3.7.2. Менеджер данных по формам

Назначение: Поиск и получение данных по формам. Класс: kz.arta.ext.interpreter.forms.search.FormsManager.

Методы:

  • getFormData(идентификатор_данных) возвращает объект FormData.

1.3.7.3. Менеджер личных карточек

Назначение: Поиск и получение личных карточек пользователей. Класс: kz.arta.ext.interpreter.forms.cards.CardsManager.

Методы:

  • getUserCard(идентификатор_формы, идентификатор_пользователя)

1.3.7.4. Файл по форме

Назначение: Подгрузка и сохранение данных по форме. Класс: kz.arta.ext.interpreter.forms.data.FormData.

Методы:

  • getValue(code)

  • setValue(code, value)

  • load()

  • save()

  • getRowsCount("id_таблицы")

  • getValue("id_таблицы", "id_компонента", номер_строки)

  • setValue("id_таблицы", "id_компонента", номер_строки, значение)

1.3.8. Использование API методов

На данный момент итерпретатор позволяет обращаться ко всем доступным методам API ARTA Synergy. Для этого нужно прописывать запросы необходимых методов непосредственно в скрипт.

Пример 1. POST-запрос API-метода

// Создаём объект POST-запроса
var post = new org.apache.commons.httpclient.methods.PostMethod("http://192.168.4.6:8080/Synergy/rest/api/storage/copy");
// Добавляем параметры согласно спецификации метода "rest/api/storage/copy"
post.addParameter("fileID", fileReportID);
post.addParameter("documentID", documentID);
// Создаём HTTP-клиент и авторизационные данные
var client = new org.apache.commons.httpclient.HttpClient();
var creds = new org.apache.commons.httpclient.UsernamePasswordCredentials(synergyUser, synergyPass);
// Задаём клиенту способ авторизации и передаём авторизационные данные
client.getParams().setAuthenticationPreemptive(true);
client.getState().setCredentials(org.apache.commons.httpclient.auth.AuthScope.ANY, creds);
// Настраиваем заголовки запроса
post.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// Выполняем метод
var status = client.executeMethod(post);
// Обязательно закрываем соединение
post.releaseConnection();
var result = true;

Пример 2. GET-запрос API-метода

// Блок аналогичен расположенному выше
var get = new org.apache.commons.httpclient.methods.GetMethod("http://127.0.0.1:8080/Synergy/rest/api/departments/list");
var client = new org.apache.commons.httpclient.HttpClient();
var creds = new org.apache.commons.httpclient.UsernamePasswordCredentials(synergyUser, synergyPass);
client.getParams().setAuthenticationPreemptive(true);
client.getState().setCredentials(org.apache.commons.httpclient.auth.AuthScope.ANY, creds);
get.setRequestHeader("Content-type", "application/json");
var status = client.executeMethod(get);

// Получаем HTTP-код возврата и преобразуем его в строку
// далее используем по своему усмотрению
var message = "" + status;
// Обязательно закрываем соединение
get.releaseConnection();
var result = true;

Пример 3. GET-запрос API-метода

// Блок аналогичен тому расположенному выше
var get = new org.apache.commons.httpclient.methods.GetMethod("http://127.0.0.1:8080/Synergy/rest/api/departments/list");
var client = new org.apache.commons.httpclient.HttpClient();
var creds = new org.apache.commons.httpclient.UsernamePasswordCredentials("ivanov", "1");
client.getParams().setAuthenticationPreemptive(true);
client.getState().setCredentials(org.apache.commons.httpclient.auth.AuthScope.ANY, creds);
get.setRequestHeader("Content-type", "application/json");
var status = client.executeMethod(get);

// Возвращает тело запроса HTTP, если такое есть, как String
var responseBody = get.getResponseBodyAsString();
var json = eval("(" + responseBody + ")");

var message = "" + status + " " + json[0].departmentID;

get.releaseConnection();

var result = true;
//
var responseBody = get.getResponseBodyAsString();
var json = eval("(" + responseBody + ")");
var message = "" + status + " " + json[0].documentID;

1.3.9. Авторизация

Так как API ARTA Synergy работает только с авторизацией модуль интерпретатор должен предоставлять возможность настраивать для каждого скрипта параметры авторизации:

  • Логин и пароль пользователя, от имени которого должен работать скрипт;

  • Ключ (для авторизации по ключам).

1.3.10. Завершение процесса

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

Модуль должен предоставлять возможность в скрипте указать как должен завершиться процесс и с каким комментарием. Для этого необходимо при завершении скрипта взять из него значения переменных:

  • result - результат:

    • true (по умолчанию) — успешно завершено;

    • false — не успешно завершено.

  • message — комментарий завершения; значение по-умолчанию вводится в метаданных скрипта.

1.3.11. Примеры скриптов

Пример 1. Сумма двух чисел внутри одной формы

Примечание

Компоненты должны быть числовыми.

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();
var summa = form.getNumericValue("cmp-a") + form.getNumericValue("cmp-b");
form.setValue("cmp-c", summa);
form.setValue("cmp-d", summa);
form.save();
var result=true;
var message = "ОК";

Пример 2. Запись значения(текст) в поле определенной личной карточки пользователя, указанного в форме

Примечание

Личную карточку после отработки процесса нужно обновить.

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();
var card= platform.getCardsManager().getUserCard('97ec70b2-a5b1-455d-86de-28555323298d', form.getValue("userID"));
card.load();
card.setValue("cmp-8", 'Привет, мир!');
card.save();
var result = true;
var message = "Успешно завершено";

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

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();
var card= platform.getCardsManager().getUserCard('97ec70b2-a5b1-455d-86de-28555323298d', form.getValue("userID"));
card.load();
card.setValue("cmp-8", form.getNumericValue("cmp-a")+form.getNumericValue("cmp-b"));
card.save();
var result = true;
var message = "Успешно завершено";

Пример 4. Разница между датой в форме и конкретным числом (количество полных дней)

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();

var d1 = form.getValue("date-a");
var year = parseInt(d1.substring(0,4));
var month = parseInt(d1.substring(5,7).replace('0', ''))-1;
var day = parseInt(d1.substring(8,10));
var hh = parseInt(d1.substring(11,13));
var mi = parseInt(d1.substring(14,16));
var sec = parseInt(d1.substring(17));
var date_1 = new Date(year, month, day, hh, mi, sec);

var date_2 = Date.parse("October 4, 2014 19:28:34 GMT");
form.setValue("cmp-2",date_2);
var dif = (date_1.getTime()-date_2)/86400000
form.setValue("cmp-1", dif);
form.setValue("cmp-2",Math.floor(dif));

form.save();
var result=true;
var message = "Привет, мир!";

Пример 5. Разница между двумя датами в личной карточке (количество полных дней), запись результата в поле формы

var form = platform.getFormsManager().getFormData (dataUUID); form.load();
var card= platform.getCardsManager().getUserCard('97ec70b2-a5b1-455d-86de-28555323298d', form.getValue("userID"));
card.load();
var d1 = card.getValue("date_a");
var year_a = parseInt(d1.substring(0,4));
var month_a = parseInt(d1.substring(5,7).replace('0', ''))-1;
var day_a = parseInt(d1.substring(8,10));
var hh_a = parseInt(d1.substring(11,13));
var mi_a = parseInt(d1.substring(14,16));
var sec_a = parseInt(d1.substring(17));
var date_1 = new Date(year_a, month_a, day_a, hh_a, mi_a, sec_a);

var d2 = card.getValue("date_b");
var year_b = parseInt(d2.substring(0,4));
var month_b = parseInt(d2.substring(5,7).replace('0', ''))-1;
var day_b = parseInt(d2.substring(8,10));
var hh_b = parseInt(d2.substring(11,13));
var mi_b = parseInt(d2.substring(14,16));
var sec_b = parseInt(d2.substring(17));
var date_2 = new Date(year_b, month_b, day_b, hh_b, mi_b, sec_b);

var dif = (date_2.getTime()-date_1.getTime())/86400000;
form.setValue("cmp-a",Math.floor(dif));
form.save();
var result=true;
var message = "Привет, мир!";

Пример 6. Количество строк в дин. таблице и сумма значений компонентов дин.таблицы

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();
form.setValue("cmp-c", form.getRowsCount("table"));
var sum=0;
for (i = 0; i < form.getRowsCount("table"); i ++)
{
sum = sum + form.getNumericValue("table", "cmp-table", i);
}
form.setValue("cmp-b",sum);
form.setValue("cmp-8",sum);
form.save();
var result=true;
var message = "ОК";

Пример 7. Код, который будет завершать процесс успешно, если значение переменной sum_one больше 100000, а неуспешно в обратном случае будет выглядеть так:

var form = platform.getFormsManager().getFormData (dataUUID);
form.load();
form.save();
if (form.getNumericValue("sum_one") < 100000){
     result = true;
     message = "Успешно отправлено по маршруту";
} else {
     result = false;
     message = "Заявка не выполнена. Сумма превышает стандарт" ;
}

Больше примеров смотрите в Cookbook.