Эта статья — набор интересных и нестандартных решений, использованных при работе с продуктом от 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 //немедленное сохранение
Немного слов о каждом режиме:
-
SAVE_AUTO(установлен по умолчанию) — это усредненный режим, который держит в буфере некоторое количество сохранений и затем пачкой пишет в БД. Вызывается стандартным образом:ArisData.Save()либоArisData.Save(Constants.SAVE_AUTO)— вернет настройки сохранения по умолчанию и сохранит данные. -
SAVE_ONDEMAND— это полностью ручной режим, когда все изменения накапливаются в сессии клиента, исполняющего скрипт, а затем массово применяются к БД. Вызывается в 2 этапа: сначала база переводится в режим сохранения по запросуArisData.Save(Constants.SAVE_ONDEMAND), а затем при необходимости записать в базу накопившиеся изменения вызываетсяArisData.Save(Constants.SAVE_NOW). -
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’а. Надеюсь, что не утомил подробностями. Возможно кто‑то воспользуется наработками и применит их в своих скриптах.
Спасибо, что дочитали.
ссылка на оригинал статьи https://habr.com/ru/articles/722418/
Добавить комментарий