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.