Как стать автором
Обновить

Снова про ARIS. Коллекция «велосипедов» для разработчика

Уровень сложностиПростой
Время на прочтение14 мин
Количество просмотров1.7K

Эта статья — набор интересных и нестандартных решений, использованных при работе с продуктом от Software AG. Я включил в нее информацию, которую сумел вспомнить и найти в своих репозиториях и старых постах для блога. Надеюсь, что эти труды не напрасны и кому‑нибудь пригодятся.

В своей предыдущей статье я рассказывал о том, кто и почему использует ARIS в 2022 году. В продолжение этой темы, спустя год (а куда торопиться?) хотелось бы написать об опыте разработок для ARIS, особенностях работы внутренней базы данных и других вещах, скрытых от глаз рядовых пользователей. Не думаю, что статья будет интересна широкому кругу читателей, но тем специалистам, которые разрабатывают скрипты и администрируют ARIS, возможно будет полезна.

Изначально я планировал сделать несколько отдельных тематических публикаций, но в процессе их написания решил, что все темы достаточно небольшие и не будет практического смысла публиковать буквально 2–3 абзаца по каждой из них — это не формат Хабра. Поэтому я объединил их в один пост, который Вы сейчас читаете.

Сразу оговорюсь, что здесь не будет места суперсовременным и модным подходам и фреймворкам: ARIS застыл хотя и не далеко в прошлом, но всё же надо сделать поправку на то, что это продукт из старого времени и вообще «энтерпрайз». Особенно это касается его серверной и клиентской версий, которые распространены на рынке намного шире нового облачного решения от Software AG. Хотя по правде говоря, именно старую версию и можно «допиливать». В новой версии на облаке едва ли получится сделать что‑то похожее.

База ARIS и ее режимы

ARIS часто интегрируют с другими системами компании: какими? — у кого на что хватит фантазии. В этих интеграциях ARIS, как правило, выступает системой‑приемником информации, потому что в большинстве случаев использования он является неким хранилищем процессов и нормативно‑справочной информации, и загрузить туда что‑нибудь уже существующее и накопленное за долгие годы работы организации — первое, что обычно приходит в голову.

Проблемы начинаются там, где загружаемой информации становится слишком много. Дело в том, что в объектной модели ARIS взаимодействие с базой осуществляется через собственные методы (API). Возможность использовать SQL отсутствует даже напрямую через СУБД, т.к. многие данные хранятся в таблицах в сериализованном виде. Поэтому единственным способом загрузить большой объем информации остаются скрипты ARIS и встроенные методы работы с данными.

К счастью, в скриптах есть несколько режимов работы с базой:

Constants.SAVE_AUTO //режим по умолчанию
Constants.SAVE_ONDEMAND //сохранение по запросу
Constants.SAVE_IMMEDIATELY //немедленное сохранение

Немного слов о каждом режиме:

  1. SAVE_AUTO (установлен по умолчанию) - это усредненный режим, который держит в буфере некоторое количество сохранений и затем пачкой пишет в БД. Вызывается стандартным образом: ArisData.Save() либо ArisData.Save(Constants.SAVE_AUTO) - вернет настройки сохранения по умолчанию и сохранит данные.

  2. SAVE_ONDEMAND - это полностью ручной режим, когда все изменения накапливаются в сессии клиента, исполняющего скрипт, а затем массово применяются к БД. Вызывается в 2 этапа: сначала база переводится в режим сохранения по запросу ArisData.Save(Constants.SAVE_ONDEMAND), а затем при необходимости записать в базу накопившиеся изменения вызывается ArisData.Save(Constants.SAVE_NOW).

  3. SAVE_IMMEDIATELY - режим, при котором каждое сохранение записывается в БД в тот же момент, когда осуществляется вызов соответствующей команды создания/удаления/изменение какого-либо объекта из кода скрипта. То есть сначала необходимо вызвать: ArisData.Save(Constants.SAVE_IMMEDIATELY). После этого все строки кода, изменяющие данные в базе, будут в тот же момент применяться к данным в БД.

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

  • создает 1000 групп (каталогов) в корневом каталоге базы;

  • создает 1000 моделей в корневом каталоге базы;

  • создает 1000 определений объектов в корневом каталоге базы;

  • создает 1000 экземпляров объектов на одной из моделей;

  • очищает базу (удаляет из нее все созданные объекты.

Код скрипта здесь, если кто-нибудь захочет воспроизвести этот тест.

Результаты работы в виде таблицы (тесты проводились на локальной машине, так что абсолютные показатели скорости работы не особо интересны, только относительные):

Режим

1000 групп

1000 моделей

1000 определений

1000 экземпляров

Очистка базы

Общее время

Разница

SAVE_AUTO

00:42,848

00:00,733

00:00,322

01:35,659

01:42,450

04:02,013

100%

SAVE_ONDEMAND

00:00,536

00:01,394

00:00,119

00:38,568

00:39,868

01:25,031

~-65%

SAVE_IMMEDIATELY

00:30,684

00:37,403

00:40,661

01:32,435

03:57,794

07:18,978

~+81%

В таблице наглядно видно, что сохранение пачками на 65% быстрее режима по умолчанию. Сохранение же каждого элемента отдельно наоборот увеличивает время выполнения на 81%.

Помимо скорости работы у режима SAVE_ONDEMAND есть еще одно недокументированное преимущество: в этом режиме можно манипулировать моделями, получать отфильтрованные модели, что-то добавлять и удалять, генерировать изображения таких измененных моделей, при этом не внося данные в базу. Это бывает очень полезно, например, когда надо вывести какую-то сложную модель, разгрузив ее от лишней информации или, наоборот, внеся какие-то изменения, которые не предполагается сохранять.

Пример:

Отфильтрованная "на лету" модель процесса
Отфильтрованная "на лету" модель процесса

Все изменения сохраняются только в той сессии, в которой исполняется скрипт, и если в коде скрипта отсутствует команда ArisData.Save(Constants.SAVE_NOW), то эти изменения не будут внесены в базу.

Если кто-нибудь захочет поэкспериментировать, то код скрипта с примером базы здесь.

Использование Java-модулей в скриптах

Тем, кто занимается разработкой скриптов ARIS, хорошо известно, что все версии продукта, начиная с 7.1, написаны на Java и исполняются в соответствующей среде. Скрипты же позволяют манипулировать данными на языке JavaScript, однако, все методы и классы ARIS — это те же Java‑библиотеки. Поэтому ничто не мешает написать свой модуль или использовать сторонний, загрузив его в соответствующую папку на сервере, либо на локальный сервер. На сервер — потому что все скрипты категории Отчеты (Reports), манипулирующие данными, исполняются на сервере в отличие от макросов, выполняющихся на стороне клиента.

Стоит упомянуть некоторые частности, поскольку те, кто пишет скрипты для ARIS зачастую немного далеки от Java (как и я когда-то):

  • если ARIS версии 7.2, то он функционирует в среде Java 1.6. Это значит, что те Java-модули, которые Вы напишите или хотите использовать должны соответствовать этой версии. И их зависимости тоже.

  • если ARIS версии 9.8, то он функционирует в среде Java 1.8. Далее - аналогично предыдущему пункту.

  • если ARIS 10 - аналогично 9.8 (но не могу быть уверен, т.к. нет доступа к этой версии).

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

Рассмотрим простой пример. Напишем Java класс из двух статичных методов для получения JSON строки из URL и превращения ее в объект.

package aris.habr;

import org.json.JSONObject;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class JavaForArisExample {
    public static String getJsonFromUrl(String url) {
        try {
            return IOUtils.toString(new URL(url), StandardCharsets.UTF_8);
        } catch (IOException e) {
            return "Error! Can't read JSON from URL!";
        }
    }

    public static JSONObject createJsonObject(String jsonString) {
        return new JSONObject(jsonString);
    }
}

Теперь необходимо собрать jar-файл и положить его на сервер ARIS. Я собрал один файл с зависимостями (см. проект на github), хотя если планируется добавлять много модулей на сервер, то лучше загружать отдельно собственные модули и отдельно jar-файлы используемых зависимостей. Это поможет в дальнейшем избежать конфликтов. Проблемы могут быть разного порядка: от кривой работы библиотек до невозможности запуска сервера.

Затем, перезапустив сервер, можно вызывать загруженный модуль из кода скрипта. Например, так:

var pack = Packages.aris.habr;
var jsonString = pack.JavaForArisExample.getJsonFromUrl("http://jsonplaceholder.typicode.com/posts/1");
var jsonObject = pack.JavaForArisExample.createJsonObject(jsonString);
var userId = jsonObject.get("userId");

* в примере умышленно использован протокол http. Если обращаться к https, то получим вот такую ошибку, которую, конечно, можно пофиксить, но это за рамками данной статьи.

Куда класть скомпилированные Java-библиотеки и зависимости на сервере / локальном сервере:

  • ARIS 7.2: (пример для локального сервера): [каталог установки]\LocalServer\lib

  • ARIS 9.8: (пример для локального сервера): [каталог установки] \ LOCALSERVER\bin\work\work_abs_local\base\webapps\abs\WEB-INF\lib

  • ARIS 10: (аналогично 9.8).

Вот собственно и все. Можно загружать Java-библиотеки и использовать их в своих скриптах. Главное не забывать о соответствии версий Java и ARIS.

Библиотека для генерации отчетов

Одним из основных назначений скриптов в ARIS являются манипуляции с данными и выдача отчетов и готовых документов в различных форматах (docx, xlsx, pdf и т. д.). Собственно скрипты и называются в ARIS отчетами - Reports. Отчеты имеют свою встроенную библиотеку для вывода данных. Она использует под собой POI библиотеку.

В те времена, когда я занимался разработкой под этот продукт, встроенная библиотека сильно отставала даже от базовых функций, доступных в офисных редакторах, и сгенерировать более‑менее сложный документ было нерешаемой задачей. Но поскольку многие продолжают пользоваться устаревшими версиями ARISа — проблема вполне вероятно может быть очень даже актуальной.

Посмотрев как формируются и выдаются отчеты, я обнаружил, что POI по сути является оберткой-реализацией для ooxml-схем, скомпилированных из стандартов ECMA-376. Встроенная же библиотека для работы с отчетами урезала и без того урезанную POI. Поэтому я решил отказаться и от встроенной библиотеки, и от POI, а взять за основу ooxml библиотеку, собранную на основе стандартов и писать обертку для нее.

Вообще я не люблю работу ради работы, поэтому сначала попытался найти какую-нибудь лазейку и просто добавить те функции вывода, которых мне не хватало (а не хватало мне сносок в конце страницы/документа и добавления встроенных файлов). Но попытки были тщетны, потому что встроенная библиотека генерировала отчеты, используя свои классы, а не POI. POI же не годился по двум причинам: там еще не были реализованы функции, которые были нужны мне и вдобавок он формировал файл, который ARIS отказывался воспринимать как результат, потому что работал только со своими обертками.

В итоге пришлось писать собственную библиотеку.

Загрузив в ARIS (как описано в предыдущем разделе этого поста) ooxml-schemas.jar, я приступил к работе. Результат получился вот такой, потому что библиотеку я решил сделать прямо в ARIS'е на Javascript, а не на Java. Вероятно, сама библиотека получилась немного громоздкой, но свои функции на тот момент она выполняла и количество кода, которое нужно было написать для вывода отчета в документ, не изменилось по сравнению со стандартными средствами.

Пример кода скрипта для вывода тестового документа новой библиотекой

*Прошу прощения за код без отступов, это просто демонстрация работы методов.

var test = new wdxWord();                                            //creates new output document;
test.setProperties({PROP_CREATOR:"Generated by wdxLibrary over OpenXML4J",PROP_SUBJECT:".docx output"})

//text styles examples
test.outSection();                                                   //outputs section without special settings
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER});
test.outText("Welcome to wdxLibrary! Enjoy it!",{RUN_BOLD:true});
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER,PAR_SPACE_AFTER:0});
test.outText("wdxLibrary is a Javascript library that works over OpenXML4J and ooxml-schemas");
test.outBreak();
test.outText("and allows to create .docx files using their functionality.");
test.outBreak();
test.outText("It's designed to get report documents with special formatting from ARIS Platform products.");
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER,PAR_SPACE_AFTER:0});
test.outText("This document generated using wdxLibrary",{RUN_FONTSIZE:20,RUN_FONT:"Courier New"});
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER,PAR_SPACE_AFTER:0});
test.outText("Copyright (c) 2017 Nikita Martyanov",{RUN_FONTSIZE:20,RUN_FONT:"Courier New"});
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER,PAR_SPACE_AFTER:0});
test.outText("https://github.com/kitmarty/wdxLibrary",{RUN_FONTSIZE:20,RUN_FONT:"Courier New"});
test.outLine();                                                      //outputs paragraph without special settings
test.outLine();                                                      //outputs paragraph without special settings
test.outText("Run without special settings");                        //outputs run without special settings
test.outLine();
test.outText("Текст кириллицей");                                    //outputs cyrillic text
test.outLine();
test.outText("Italic",{RUN_ITALIC:true});                            //outputs italic
test.outLine();
test.outText("Bold",{RUN_BOLD:true});                                //outputs bold
test.outLine();
test.outText("Italic&Bold",{RUN_ITALIC:true,RUN_BOLD:true});         //outputs bold and italic simultaneously
test.outLine();
test.outText("Single underline ",{RUN_UNDERLINE:wdxConstants.UL_SINGLE});
test.outText("Wavy heavy underline",{RUN_UNDERLINE:wdxConstants.UL_WAVY_HEAVY});
test.outText("superscript",{RUN_SCRIPT:wdxConstants.SCR_SUPERSCRIPT});
test.outLine();
test.outText("All letters are capitals ",{RUN_CAPS:true});
test.outText("subscript",{RUN_SCRIPT:wdxConstants.SCR_SUBSCRIPT});
test.outLine();
test.outText("All letters are small capitals",{RUN_SMALL_CAPS:true});
test.outLine();
test.outText("Strikethrough text",{RUN_STRIKE:true});
test.outLine();
test.outText("Highlighted text",{RUN_HIGHLIGHT:wdxConstants.HL_CYAN});
test.outLine();
test.outText("Additional info about properties and constants you can find in wdxLibrary.js.");
test.outLine();
test.outText("Also you can read ECMA standarts and explore ooxml-schemas-x.x.jar for more functionality. It's easy to add to this library special formatting you need.");
test.outLine();
test.outText("Below you can find examples of interesting formatting such as footnotes, endnotes, embedded files etc.");

//page orientation, page size and page columns example
test.outSection({PAGE_COL_COUNT:3,PAGE_ORIENT:wdxConstants.PAGE_ORIENT_LANDSCAPE,PAGE_SIZE:wdxConstants.PAGE_SIZE_A4});
test.outLine();
test.outText("This section has 3 columns and landscape orientation.");
test.outBreak({BREAK_TYPE:wdxConstants.BREAK_COLUMN});
test.outText("Text in the second column");
test.outBreak({BREAK_TYPE:wdxConstants.BREAK_COLUMN});
test.outText("Text in the third column");

//footnotes and endnotes
test.outSection({SECTION_ENDNOTE:{EDN_NUMFMT:wdxConstants.NUMFMT_UPPER_LETTER}});
test.outLine();
test.outText("Text in paragraph with a footnote");
test.outFootnote({RUN_BOLD:true},{},{RUN_COLOR:"AA4455"});           //reference in text - bold, color of reference in footnote - AA4455
test.outText("footnote description without special formatting");
test.endFootnote();
test.outText(".");
test.outLine();
test.outText("Text in paragraph with an endnote");
test.outEndnote({RUN_BOLD:true},{},{RUN_COLOR:"CC8855"});           //reference in text - bold, color of reference in endnote - CC8855
test.outText("endnote description without special formatting");
test.endEndnote();
test.outText(". Description of this endnote you can find in the end of the document.");
test.outLine();
test.outText("Endnotes numbering format is upper letter. It's defined in the section properties");

//headers and footers
test.outSection();
test.outLine();
test.outText("This section has a header.");
test.outHeader();
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_CENTER});
test.outText("Bold text in header",{RUN_BOLD:true});
test.endHeader();
test.outLine();
test.outText("This text outputs after the header has been put in the file.");
test.outFooter();
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_RIGHT});
test.outField(wdxConstants.FLD_PAGE,{RUN_UNDERLINE:wdxConstants.UL_SINGLE});
test.endFooter();
test.outLine();
test.outText("This section also has a footer which contents page field with special formatting.");

//tables and bookmarks
test.outSection({SECTION_TYPE:wdxConstants.SM_NEXT_PAGE});//check wdxLibrary.js for more options of sections
test.outHeader();//if you don't want to have header from previos page call empty header. footer is same
test.endHeader();
test.outFooter();
test.endFooter();
//when you create table you can define border style before
//you can define every property of the table you find in ooxml-schemas
//it's just simple example
var brdStyle = {
    BORDER_COLOR:"243A84",
    BORDER_STYLE:wdxConstants.BORDER_DOT_DASH
};
var cellBrdStyle = {
    TBL_CELL_BORDER_TOP:brdStyle,
    TBL_CELL_BORDER_BOTTOM:brdStyle,
    TBL_CELL_BORDER_LEFT:brdStyle,
    TBL_CELL_BORDER_RIGHT:brdStyle
};
var cellStyle = {
    TBL_CELL_BORDERS:cellBrdStyle,
};
var bm1 = test.addBookmarkStart("Bookmark_name");//adding bookmark for example of internal hyperlink in the next section
test.outLine();
test.outText("This section demonstrates tables.");
test.addBookmarkEnd();
test.outLine();
test.outText("This section doesn't have header and footer.");
test.outTable({TBL_LAYOUT:wdxConstants.TBL_LAYOUT_AUTOFIT,TBL_WIDTH:{TBL_S_TYPE:wdxConstants.TBLW_PCT,TBL_S_WIDTH:5000}});
test.outRow();
test.outCell(cellStyle);
test.outLine();
test.outText("Colored text in cell",{RUN_COLOR:"AB1321"});
test.outCell(cellStyle).setStyle({TBL_CELL_SHADING:{TBL_SHADING_FILL:"AAAAAA"},TBL_CELL_WIDTH:{TBL_S_TYPE:wdxConstants.TBLW_PCT,TBL_S_WIDTH:2500}});
test.outLine();
test.outText("This table has two cells. Table is 100% width of the page, and cells are 50% width of the table.");
test.outBreak();
test.outText("This cell has #AAAAAA fill color.");
test.endTable();

//hyperlinks and numbering (lists)
test.outSection();
test.outLine();
test.outText("In the previous section I've added a bookmark to check this function.");
test.outLine();
test.outHyperlink("Hyperlink to the bookmark of the previous section",String(bm1.item.getName()));
test.outLine();
test.outText("Next hyperlink is external. Project page will be opened in default browser.");
test.outLine();
test.outHyperlink("https://github.com/kitmarty/wdxLibrary/",null,"https://github.com/kitmarty/wdxLibrary/");

//I don't like the numbering solution, but it works. Maybe I'll do it better if I have more spare time
var num = test.createNum();
num.setStyle({NUM_LVL:0});//0 - just to call case in switch block
num.setStyle({NUM_FMT:wdxConstants.NUMFMT_UPPER_ROMAN,NUM_ILVL:0,NUM_LVL_START:1,NUM_LVL_TEXT:"%1.",NUM_PSTYLE:{PAR_IND_LEFT:720,PAR_HANGING:360,PAR_CONT_SPACING:true}});
num.setStyle({NUM_LVL:0});
num.setStyle({NUM_FMT:wdxConstants.NUMFMT_DECIMAL,NUM_ILVL:1,NUM_LVL_START:1,NUM_LVL_TEXT:"%2)",NUM_PSTYLE:{PAR_IND_LEFT:1080,PAR_HANGING:360,PAR_CONT_SPACING:true}});
num.setStyle({NUM_LVL:0});
num.setStyle({NUM_FMT:wdxConstants.NUMFMT_BULLET,NUM_ILVL:2,NUM_LVL_START:1,NUM_LVL_TEXT:"",NUM_PSTYLE:{PAR_IND_LEFT:1440,PAR_HANGING:360,PAR_CONT_SPACING:true}});

test.outLine();
test.outText("There is an example of multilevel numbered (and bulleted) list below.");
test.outLine({PAR_LIST:0,PAR_LIST_ID:1});
test.outText("First level of the list. Upper Roman numbering format.");
test.outLine({PAR_LIST:1,PAR_LIST_ID:1});
test.outText("Second level of the list (decimal numfmt)");
test.outLine({PAR_LIST:1,PAR_LIST_ID:1});
test.outText("Second level of the list");
test.outLine({PAR_LIST:2,PAR_LIST_ID:1});
test.outText("Third level of the list (bullets)");
test.outLine({PAR_LIST:2,PAR_LIST_ID:1});
test.outText("Third level of the list (bullets)");
test.outLine({PAR_LIST:0,PAR_LIST_ID:1});
test.outText("First level of the list");

//pictures and embedded files
test.outSection();
test.outLine();
test.outText("There is an embedded file below (double click). ");
test.outText("Thumbnail generates on the fly using Java libraries. ");
test.outText("You can change it for ordinary MS Word icon but ");
test.outText("I don't know anything about copyrights that's why I've done this weird solution.");
test.outLine();
test.outEmbeddedFile(Context.getFile("wdxLibrary_empty.docx",Constants.LOCATION_COMMON_FILES),wdxConstants.FILE_TYPE_DOCX);
test.outLine();
test.outText("In this section you also can find examples of graphic output.");
test.outLine();
test.outText("The header of the section contents wdxLibrary logo and the page body contents picture of context model.");
test.outHeader();//if you don't want to have header from previos page call empty header. footer is same
test.outLine({PAR_ALIGN:wdxConstants.ALIGN_RIGHT});
test.outGraphic(Context.getFile("wdxLibrary.png",Constants.LOCATION_COMMON_FILES),wdxConstants.FILE_TYPE_PNG,75);
test.endHeader();
test.outLine();
test.outGraphic(ArisData.getSelectedModels()[0].Graphic(false,false,1049),wdxConstants.FILE_TYPE_EMF,-1);

//native styles
test.outSection();
test.outLine();
test.outText("This section has the same header as in the previous section.");
test.outLine();
test.outText("wdxLibrary also allows you to define 'native' styles of the document. There is a paragraph below where the 'native' style is applied.");
test.createStyle({STYLE_TYPE:wdxConstants.STYLE_TYPE_PAR,STYLE_NAME:"Custom style by wdxLibrary",STYLE_BASED_ON:"Base",STYLE_RUN:{RUN_BOLD:true,RUN_UNDERLINE:wdxConstants.UL_WAVY_HEAVY},STYLE_PAR:{PAR_IND_LEFT:500}});
test.outLine({PAR_PSTYLE:"Custom style by wdxLibrary"});
test.outText("Text with the style that calls 'Custom style by wdxLibrary'");

test.writeReport();//you can specify filename or leave it empty.

Кроме работы с docx документами, можно было реализовать генерацию и xlsx, и pptx, но моего энтузиазма в тот момент хватило только на отчеты в формате Word :)

На этом я бы хотел закончить свой рассказ про «костыли и велосипеды» для ARIS'а. Надеюсь, что не утомил подробностями. Возможно кто‑то воспользуется наработками и применит их в своих скриптах.

Спасибо, что дочитали.

Теги:
Хабы:
Всего голосов 2: ↑2 и ↓0+2
Комментарии9

Публикации

Истории

Работа

Java разработчик
361 вакансия
React разработчик
60 вакансий
ABAP разработчик
10 вакансий

Ближайшие события