JavaScript интерпретатор (далее просто «интерпретатор») - внешний модуль, предоставляющий возможность написать server-side скрипт на JavaScript с использованием объектов платформы. На данный момент использовать интерпретатор можно для обработки в блокирующем процессе. Поддерживаемые объекты: формы, личные карточки. С помощью интерпретатора есть возможность решать такие задачи, как арифметические действия с числовыми полями, с датами, производить необходимые расчеты в динамических таблиц и т.д.
Данная документация актуальна для версии 2.63 - 3.15.
В данном разделе описывается автоматический способ установки интерпретатора используя средства управления программными пакетами операционных систем, базирующихся на GNU/Debian Linux. В случае возникновения проблем во время установки пакета Вы можете воспользоваться устаревшим ручным способом.
Установите пакет arta-synergy-interpreter:
aptitude install arta-synergy-interpreter
В процессе установки будут запрошены:
пароль для доступа к MySQL, т.к. установка требует модификации этой базы данных;
логин и пароль пользователя, который будет использован для создания отчетов об ошибках скриптов (по умолчанию используется «1»).
После установки и перезагрузите JBoss.
Интерпретатор будет доступен по адресу:
http://адрес_сервера:http_порт_сервера/interpreter/
Создать базу данных mysql (с кодировкой UTF-8):
mysql -uroot -proot CREATE DATABASE interpreter CHARACTER SET utf8 ; use interpreter
Выполнить в базе данных скрипт:
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));
Настроить на нее 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&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>
Создать на сервере приложений очередь с 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>
Связать очередь и процесс через конфигурационный файл
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>
Установить на сервер 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
Перезапустите сервер.
Создать файл 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
Включить режим логирования 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>
Перезапустите сервер.
Модуль «Интерпретатор» не имеет встроенной защиты от
несанкционированного входа - защитить его можно внешним образом,
например, используя nginx.
Ниже приведём пример с установкой защиты от входа в модуль при
помощи web-сервера nginx, его модулей
http_auth_request_module,
headers-more-nginx-module
и метода REST API Synergy
rest/api/auth/{role}.
Будем предполагать, что используется стандартный
конфигурационный файл для nginx, поставляемый
вместе с Synergy, synergy-base.
Веб-сервер 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, соответствуют пользователю,
который
имеет доступ в систему и
обладает указанной ролью,
метод вернёт код 200, в обратном случае -
403, а при отсутствии заголовка
Authorization - 401.
Модуль headers-more-nginx-module
понадобится нам для того, чтобы заменить содержимое заголовка
WWW-Authenticate, которое передаёт API
Synergy - в целях упрощения интеграции внешнего проигрывателя
там сейчас передаётся None вместо
Basic, а стандартная директива
nginx, add_header, не
срабатывает при 401 коде ответа от прокси.
Для начала необходимо установить пакет
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»
(«суперметодолог»).
При переходе к модулю «Интерпретатор» открывается следующее окно:
В данном окне отображается список скриптов, которые можно редактировать и удалять, нажав соответствующую кнопку. Чтобы вставить новый скрипт, необходимо нажать кнопку «Добавить». Открывается окно:
Данное окно делится на две области: метаданные и код. Метаданные:
Название — название скрипта в формате
event.blocking.interpreter.%название_скрипта%;
Описание;
Комментарий по умолчанию;
Авторизация.
В окне кода прописывается сам скрипт.
Скрипт может обращаться к параметрам авторизации, которые
указаны в интерпретаторе, с помощью строковых переменных
login, password и
key.
Не забудьте сохранить написанный скрипт!
Написанный скрипт можно запустить непосредственно из
интерпретатора с помощью кнопки «Запустить» или
нажатием клавиш Ctrl+S. При первом запуске скрипта открывается
окно «Конфигурация выполнения скрипта», где можно
ввести значения параметров dataUUID,
documentID и executionID -
эти параметры передаются скрипту интерпретатора при его запуске
в
блокирующем
процессе Synergy:
Примечание:
Формально эти параметры не обязательны для запуска скрипта, но выполнение скрипта без них может приводить к ошибкам.
Если флаг «Открывать конфиг при запуске»
установлен, то это окно будет отображаться при каждом запуске
скрипта, иначе - по нажатию на кнопку
.
Результат выполнения скрипта отображается в виде всплывающего сообщения в нижней правой части экрана:
Если при выполнении возникли ошибки, то они также отображаются во всплывающем сообщении:
Встроенный в java интерпретатор позволяет передавать Java объекты JavaScript-у, поэтому модуль интерпретатор предоставляет пользователю:
Объект платформы с названием platform или synergy;
Все строки, пришедшие как входные данные в блокирующий процесс;
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);
}
Назначение: Отвечает за создание экземпляров других объектов.
Класс:
kz.arta.ext.interpreter.platform.Platform.
Методы:
getFormsManager() возвращает «Менеджер
данных по формам»;
getCardsManager() возвращает «Менеджер
личных карточек».
Назначение: Поиск и получение данных по формам. Класс:
kz.arta.ext.interpreter.forms.search.FormsManager.
Методы:
getFormData(идентификатор_данных)
возвращает объект FormData.
Назначение: Поиск и получение личных карточек пользователей.
Класс:
kz.arta.ext.interpreter.forms.cards.CardsManager.
Методы:
getUserCard(идентификатор_формы, идентификатор_пользователя)
Назначение: Подгрузка и сохранение данных по форме. Класс:
kz.arta.ext.interpreter.forms.data.FormData.
Методы:
getValue(code)
setValue(code, value)
load()
save()
getRowsCount("id_таблицы")
getValue("id_таблицы", "id_компонента", номер_строки)
setValue("id_таблицы", "id_компонента", номер_строки, значение)
На данный момент итерпретатор позволяет обращаться ко всем доступным методам 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;
Так как API ARTA Synergy работает только с авторизацией модуль интерпретатор должен предоставлять возможность настраивать для каждого скрипта параметры авторизации:
Логин и пароль пользователя, от имени которого должен работать скрипт;
Ключ (для авторизации по ключам).
Блокирующий процесс может завершиться как успешно так и неуспешно. В обоих случаях необходимо передавать комментарий, говорящий о результате завершения процесса.
Модуль должен предоставлять возможность в скрипте указать как должен завершиться процесс и с каким комментарием. Для этого необходимо при завершении скрипта взять из него значения переменных:
result - результат:
true (по умолчанию) — успешно завершено;
false — не успешно завершено.
message — комментарий завершения; значение по-умолчанию вводится в метаданных скрипта.
Пример 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.