Разработка плагинов для Visual Paradigm

от автора

Visual paradigm ― мощный инструмент, идеология использования которого выходит за рамки простого рисования диаграмм. Главное назначение инструментов данного класса (Visual Paradigm, Enterprise Architect и др.) ― описание модели информационной системы и дальнейшая работа с ней. Под работой подразумевается ее визуализация в виде диаграммы, экспорт документации, генерация исходных кодов, анализ, подсчет метрик и т. п. 

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

В статье я расскажу об основах создания плагинов для Visual Paradigm. В качестве примера возьмем формирование SQL-скрипта с комментариями к таблицам и колонкам на основе ER-диаграммы.

Что такое плагин Visual Paradigm

Плагин Visual Paradigm ― набор скомпилированных для JVM классов, реализующих интерфейсы Visual Paradigm OpenAPI. Плагин может быть написан на любом JVM языке: Java, Kotlin, Scala, Groovy и т. д. Единственное ограничение ― использование JDK версии 11 (для 16-x и 17-х версий Visual Paradigm). 

Плагины делятся на три группы:

  • привязаны к контексту и доступны только через контекстное меню (можно определить, для какого именно типа модели или диаграммы будет доступно действие); 

  • не привязанные к контексту (доступны через кнопку на панели);

  • создание собственных фигур. 

Любой плагин для Visual Paradigm состоит из двух частей:

  • классов, которые реализуют логику плагина и взаимодействуют с проектом через OpenAPI Visual Paradigm;

  • манифеста ― описания, как интегрировать плагин в интерфейс. 

Все интерфейсы, предоставленные Visual Paradigm для разработки плагинов, собраны в библиотеку OpenAPI. Она находится в папке «bundled» в каталоге установки Visual Paradigm. Каждый плагин должен иметь класс с реализацией интерфейса VPPlugin и один или несколько ―с реализацией интерфейсов действий (action controller):

  • VPActionController ― для общих действий, не привязанных к контексту;

  • VPContextActionController ― для контекстно-зависимых действий;

  • VPShapeController ― для создания собственных фигур.

Манифест ― это файл plugin.xml, описывающий следующие метаданные плагина:

  • где и как отобразить плагин;

  • область применения (например, диаграмма конкретного типа);

  • связь интерфейса пользователя и классов действий.

Манифест, классы, ресурсы и зависимости плагина кладутся в каталог plugins, находящийся в каталоге настроек пользователя Visual Paradigm. Найти нужный путь можно в окне установки плагина Help -> Install plugin. 

Для отладки загрузки плагина и, в первую очередь, корректности plugin.xml, нужно смотреть файл vp.log, который находится в каталоге настроек пользователя Visual Paradigm. В этот файл попадает весь System.out (в Kotlin для записи в System.out используется функция println).

Установка плагинов

Установка уже разработанных плагинов доступна через Help -> Install plugin. Есть три способа:

  • из ZIP архива;

  • из каталога;

  • скопировать вручную в папку плагинов пользователя (в этом случае Visual Paradigm предлагает скопировать путь к каталогу плагинов). 

Экран установки плагина в Visual Paradigm (Help -> Install Plugin)» title=»Экран установки плагина в Visual Paradigm (Help -> Install Plugin)» width=»331″ height=»215″ data-src=»https://habrastorage.org/getpro/habr/upload_files/c27/eef/f6a/c27eeff6a9b751a40599e39948b58111.png»/><figcaption>Экран установки плагина в Visual Paradigm (Help -> Install Plugin)</figcaption></figure>
<p>Результат всегда один ― файлы окажутся в папке плагинов. После установки плагина надо перезагрузить Visual Paradigm. </p>
<p>Всем разработчикам плагинов я рекомендую установить плагин Hot Reload (<a href=https://github.com/leonidv/vp-plugin-hot-reload/releases/tag/1.0). Он позволяет избегать перезагрузки Visual Paradigm при разработке плагина в том случае, если поменялся только его исходный код. Когда меняется plugin.xml или состав подключаемых библиотек, перезагружать программу все равно придется. Hot Reload не зависит от контекста, поэтому после установки он будет доступен на вкладке Plugins (кнопка Reload plugins).

Кнопка установленного плагина Hot Reload
Кнопка установленного плагина Hot Reload

Шаги разработки плагина

Разработка плагина для Visual Paradigm состоит из следующих шагов:

  1. Подготовка проекта плагина, включая:

    1. Создание пустого Gradle (Maven) проекта.

    2. Подключение к нему openapi.jar.

    3. Настройка Gradle-задач для удобной установки разрабатываемого плагина в Visual Paradigm.

  2. Разработка самого плагина (здесь порядок действий достаточно произвольный):

  1. Реализация VPPlugin.

  2. Реализация действия (самой логики).

  3. Описание манифеста ― plugin.xml.

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

Подготовка заготовки плагина

В данном примере будут использованы следующие инструменты:

  • Kotlin;

  • Gradle, в качестве языка описания конфигурации ― Kotlin;

  • IntelliJ Idea (возможностей бесплатной версии будет достаточно).

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

Шаг 1a. Создание пустого Gradle-проекта

Создать Gradle-проект можно любым способом. Я предпочитаю делать это с помощью IntelliJ Idea, указав следующие параметры:

  • Language: Kotlin;

  • Build system: Gradle;

  • JDK: 11 (я рекомендую сборки LibericaJDK);

  • Gradle DSL: Kotlin.

Шаг 1b. Подключение openapi.jar

После создания проекта нужно сделать два шага:

  1. Скопировать файл openapi.jar в папку проекта lib (ее нужно создать). При стандартной установке в Windows openapi.jar можно найти по пути: C:\Program Files\Visual Paradigm 16.3\bundled.

  2. Добавить его в зависимости скрипта сборки:
    compileOnly(files("lib/openapi.jar")).

Шаг 1c. Gradle-задачи для деплоя плагина

Это минимально рабочий проект (по сути, достаточно указать одну зависимость). А для удобной разработки нам понадобится еще ряд вспомогательных задач (основные):

  • установка плагина;

  • копирование только скомпилированных классов, что в связке с плагином для hot reload значительно повышает удобство разработки. 

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

import org.apache.tools.ant.taskdefs.Mkdir import org.jetbrains.kotlin.gradle.tasks.KotlinCompile  plugins {     kotlin("jvm") version "1.7.10" }  group = "com.vygovskiy.vpplugin" version = "1.0-SNAPSHOT"  val userHome = System.getProperty("user.home") val userName = System.getProperty("user.name") val vpPluginsDir = "${userHome}\\AppData\\Roaming\\VisualParadigm\\plugins" val vpPluginName = project.name val vpPluginDir = "$vpPluginsDir/$vpPluginName"  repositories {     mavenCentral() }  dependencies {     compileOnly(files("lib/openapi.jar"))     testImplementation(kotlin("test")) }  tasks.test {     useJUnitPlatform() }  tasks.withType<KotlinCompile> {     kotlinOptions.jvmTarget = "11" }  val createPluginDir by tasks.register("vpCreatePluginDir") {     group = "Visual Paradigm"     description = "Create plugin's directory"      doLast {         mkdir(vpPluginDir)     } }  tasks.register("vpInstallPlugin") {     group = "Visual Paradigm"     description = "Copy all plugins file into Visual Paradigm plugins folder"      dependsOn(         copyClasses,         copyDependenciesJars     )  }  val deletePluginDir by tasks.register<Delete>("vpDeletePluginDir") {     group = "Visual Paradigm"     description = "Delete plugin's folder"     delete(vpPluginDir) }  val copyClasses by tasks.register<Copy>("vpCopyClasses") {     group = "Visual Paradigm"     description = "Compile and copy classes into plugins folder. Use it for hot-reload "      dependsOn("build")     from(         "$buildDir/classes/kotlin/main",         "$buildDir/resources/main"     )     into(vpPluginDir) }  val copyDependenciesJars by tasks.register<Copy>("vpCopyDependenciesJars") {     group = "Visual Paradigm"     description = "Copy plugin's dependencies into plugin folder"      dependsOn(copyPluginXml)      from(         getJarDependencies()             .map {it.absoluteFile}             .toTypedArray()     )     into("$vpPluginDir/lib") }  val copyPluginXml by tasks.register("vpCopyPluginXml") {     group = "Visual Paradigm"     description = "Prepare and copy plugin.xml into plugin folder"      dependsOn(createPluginDir)      doLast {         val runtimeSection = getJarDependencies().joinToString(             separator = "\n",             prefix = "\n  <runtime>\n",             postfix = "\n  </runtime>\n"         ) {"    <library path='lib/${it.name}' relativePath='true'/>"}          val pluginXmlFile = File("$buildDir/resources/main/plugin.xml").readText()         val content = pluginXmlFile             .replace("<runtime/>", runtimeSection, false)             .replace("#pluginId#", vpPluginName)             .replace("#user#", userName)         File("$vpPluginDir/plugin.xml").writeText(content)     } }  fun getJarDependencies(): FileCollection {     return configurations.runtimeClasspath.get()         .filter {it.name.endsWith("jar")} }

Список всех добавленных команд с описанием:

Список команд Gradle-скрипта, помогающих в разработке плагинов Visual Paradigm
Список команд Gradle-скрипта, помогающих в разработке плагинов Visual Paradigm

Разработка логики экспорта комментариев

Теперь у нас все готово для удобной реализации изначально поставленной задачи ― выгрузки описания колонок и таблица из ER-диаграмм в виде SQL кода для Postgresql. Данное действие лучше добавить в контекстное меню ER-диаграммы, т. е. оно контекстно-зависимое. Поэтому:

  • класс реализует интерфейс VPContextActionController;

  • в манифесте мы используем блок contextSensitiveActionSet.

Реализация VPPlugin

Реализация интерфейса VPPlugin обычно остается пустой. Интерфейс описывает два метода, вызываемых при загрузке (loaded) и выгрузке (unloaded) классов плагина. При желании можно выводить информацию в System.out и читать ее из файла vp.log (файл находится в каталоге уровнем выше каталога с плагинами). 

Обратите внимание! VP создает инстанс класса для вызова loaded и отдельный инстанс для unloaded.

package ercomments  import com.vp.plugin.VPPlugin import com.vp.plugin.VPPluginInfo  class Plugin : VPPlugin {      override fun loaded(vpPluginInfo: VPPluginInfo) {         // Выводим сообщение в стандартный вывод. Вы можете увидеть сообщение         // в файле vp.log, который лежит в каталоге настроек пользователя VP.         // В этом же каталоге находится папка plugins.         println("plugin [ercomments] is loaded ")    }      override fun unloaded() {         println("[ercomments] is unloaded")     } } 

Реализация класса действий

Интерфейс VPContextActionController

Класс с действием должен реализовать интерфейс VPContextActionController с двумя функциями:

  • performAction ― здесь надо поместить логику плагина;

  • update ― вызывается при отображении действия в меню (в реализации можно, например, заблокировать элемент меню). 

Самый важный параметр обеих функций ― параметр с типом VPContext. Из него можно получить диаграмму или ее элемент, на котором было вызвано действие. 

Элементы модели и элементы диаграммы

Важно различать элемент диаграммы и элемент модели. Элемент диаграммы ― графический элемент с координатами, стилем и т. п. Он принадлежит конкретной диаграмме, и все изменения будут видны только на ней. Как правило, каждый элемент диаграммы отображает свойства одного элемента модели. 

Элемент модели ― это часть модели проекта. Он содержит такие свойства, как имя, тип, описание (description), комментарии, видимость, операции и т. д. Полный список типов задан константами MODEL_TYPE_* в интерфейсе IModelElementFactory

Важно! Один элемент модели может быть представлен разными элементами диаграммы. Если изменяется свойство элемента модели (например, имя) на одной диаграмме, оно изменится и на других. 

В большинстве случаев нужно работать с элементами модели. Для получения списка элементов модели проще всего воспользоваться методом диаграммы toDiagramElementArray() и вызывать у каждого элемента диаграммы функцию getModelElement(). Для удобства, при вызове toDiagramElementArray() можно передать список типов моделей.

Интерфейс пользователя

Visual Paradigm реализована на фреймворке Swing и позволяет в плагинах отображать любые окна. Информация о создании полноценного UI выходит за рамки этой статьи.

Для нашей задачи необходимо узнать у пользователя имя файла, в который будет выгружен SQL. Для решения этой задачи подойдет стандартный Java-класс JFileChooser. Прочитать про него можно здесь: https://docs.oracle.com/javase/tutorial/uiswing/components/filechooser.html 

Исходный код реализации плагина

Реализация плагина заключается в следующих шагах:

  1. Узнаем у пользователя имя файла, в который он хочет экспортировать SQL (функция chooseFile).

  2. Получаем все элементы модели типа DB_TABLE.

  3. Для каждой таблицы:

    • формируем и сохраняем SQL с ее комментарием;

    • перебираем строки и для каждой сохраняем SQL с комментарием.

Исходный код:

package ercomments  import com.vp.plugin.ApplicationManager import com.vp.plugin.ViewManager import com.vp.plugin.action.VPAction import com.vp.plugin.action.VPContext import com.vp.plugin.action.VPContextActionController import com.vp.plugin.model.IDBTable import com.vp.plugin.model.factory.IModelElementFactory import java.awt.Component import java.awt.event.ActionEvent import java.io.PrintWriter import javax.swing.JFileChooser  class ExportERCommentsPluginAction : VPContextActionController {      private val TABLE_COMMENT = """COMMENT ON TABLE #TABLE_NAME IS '#COMMENT';"""     private val COLUMN_COMMENT = """COMMENT ON COLUMN #TABLE_NAME.#COLUMN_NAME IS '#COMMENT';"""      private val viewManager : ViewManager             get() = ApplicationManager.instance().viewManager      private fun makeCommentValue(s: String?): String =         if (s.isNullOrBlank()) {             "NULL"         } else {             s.replace("'", "''")         }      private fun makeTableComment(name: String, comment: String): String =         TABLE_COMMENT             .replace("#TABLE_NAME", name)             .replace("#COMMENT", makeCommentValue(comment))      private fun makeColumnComment(table: String, column: String, comment: String): String =         COLUMN_COMMENT             .replace("#TABLE_NAME", table)             .replace("#COLUMN_NAME", column)             .replace("#COMMENT", makeCommentValue(comment))      /**      * Предлагаем пользователю выбрать файл и если он выбран - вызываем action.      */     private fun chooseFile(action: (PrintWriter) -> Unit) {         val fc = viewManager.createJFileChooser()         if (fc.showSaveDialog(viewManager.rootFrame) == JFileChooser.APPROVE_OPTION) {             val file = fc.selectedFile             viewManager.showMessage("Export ER Comments into ${file.absoluteFile}")             file.printWriter().use {                 action(it)                 it.flush()             }             viewManager.showMessage("export completed")         }     }      override fun performAction(action: VPAction, context: VPContext, event: ActionEvent) {         chooseFile {writer ->              val diagram = context.diagram              val tables = diagram                 // Получаем массив всех элементов диаграммы, которые относятся к таблицам                 .toDiagramElementArray(IModelElementFactory.MODEL_TYPE_DB_TABLE)                 // Достаем из элемента диаграммы элемент модели                 .map {it.modelElement as IDBTable}              tables.forEach {table ->                 viewManager.showMessage("  export table ${table.name}")                 writer.println(makeTableComment(table.name, table.description))                  table.toDBColumnArray().forEach {column ->                     writer.println(makeColumnComment(table.name, column.name, column.description))                 }             }         }     }      override fun update(vpAction: VPAction, vpContext: VPContext) { } }

Манифест плагина

Теперь давайте добавим плагин в UI Visual Paradigm. Для этого нужно создать манифест плагина ― plugin.xml. Его нужно сохранить в каталоге проекта src/main/resources/ Нужный манифест выглядит таким образом:

<?xml version="1.0" encoding="UTF-8" ?>  <!--    Общее описание плагина. Ключевое здесь - аттрибут class - имя          класса плагина, который реализует интерфейс com.vp.plugin.VPPlugin --> <plugin         id="ercomments"         name="ER Comments exporter"         description="Export only comments from ER Diagram"         provider="Leonid Vygovskiy"         class="ercomments.Plugin">      <!-- Место-заглушка для путей к используемым библиотекам.           Заполняется динамически с помощью gradle -->     <runtime/>      <actionSets>         <contextSensitiveActionSet id="ercomments.Export">             <!-- Определяем контекст действия -->             <contextTypes all="false">                 <include type="ERDiagram"/>             </contextTypes>              <!--                  menuPath:                    Пункт контекстного меню, после которого будет выведен                     наш пункт.                    Для Open Specification... нужно указать OpenSpecification.             -->             <action                     style="normal"                     menuPath="Export"                     id="ercomments.Export"                     label="Export comments as DDL">                 <actionController class="ercomments.ExportERCommentsPluginAction"/>             </action>         </contextSensitiveActionSet>     </actionSets> </plugin> 

Разберем его подробнее: 

  1. Объявление плагина корневым тэгом plugin. В его атрибутах задается метаинформация о плагине (id, имя и т. п.) и указывается класс реализации VPPlugin.

  2. Информация об используемых зависимостях ― секция runtime. В нашем примере она создается автоматически Gradle-скриптом.

  3. В тэге actionSets задаются действия (action) и правила их отображения на UI.

Самое интересное и значимое в plugin.xml ― это определение действия. В actionSets можно вложить два элемента:

  • actionSet ― для определения контекстно-независимого действия;

  • contextSensitiveActionSet ― для определения контекстно-зависимого действия. 

Действие экспорта описания таблиц и колонок в SQL имеет смысл только для ER-диаграмм, т. е. оно контекстно-зависимое. Определим это с помощью блока. 

Иногда бывает трудно понять, какой именно тип имеет нужная нам диаграмма или элемент модели. Документация здесь, откровенно, хромает. Чтобы определить тип документа, проще всего включить режим all=”true” (отображать в любом контекстном меню) и использовать следующий код в реализации действия: 

override fun performAction(action: VPAction, context: VPContext, event: ActionEvent) {     val viewManager = ApplicationManager.instance().viewManager     viewManager.showMessage(context.contextType)     viewManager.showMessage(context.diagram?.type ?: "")     viewManager.showMessage(context.modelElement?.modelType ?: "") }

В атрибуте menuPath указывается пункт меню, после которого будет добавлен наш пункт. В данном случае ― сразу после подменю Export. Чтобы добавить сразу после пункта меню Open Specification, надо указать “OpenSpecification”. 

Пример оформления контекстно-независимого плагина можно посмотреть в реализации плагина Hot Reload: https://github.com/leonidv/vp-plugin-hot-reload/blob/master/src/main/resources/plugin.xml#L13 

Деплой плагина

Последнее, что осталось сделать, ― установить плагин в Visual Paradigm:

  1. Вызвать задачу Gradle-скрипта vpInstallPlugin.

  2. Запустить Visual Paradigm.

  3. Проверить, что в контекстном меню ER-диаграмм появился пункт Export descriptions as DDL.

На рисунке показан пункт в меню Visual Paradigm и пример полученного SQL-файла.

Что делать, если что-то пошло не так

Логи и сообщения

Важно отметить, что в реальной жизни я делаю плагин в такой последовательности:

  1. Делаю реализацию VPActionController/ VPContextActionController, которая в функции performAction выводит строку в панель сообщений Visual Paradigm:
    ApplicationManager.instance().viewManager.showMessage(":)")

  2. Добавляю действие в plugin.xml и убеждаюсь, что он появился в нужном месте.

  3. Реализую логику работы плагина. 

При появлении проблемы, полезно смотреть в vp.log Visual Paradigm. Он находится в той же папке, что и plugins с нашим плагином. В него попадают:

  • ошибки разбора plugin.xml (например, опечатка в имени класса);

  • не перехваченные исключения.

Для отладки логики и вывода промежуточных сообщений лучше использовать viewManager. showMessage. 

Отладка в дебаггере

Если ваш плагин имеет очень сложную логику и требуется полноценная отладка, вы можете запускать Visual Paradigm под дебаггером IDE. Как это сделать, хорошо описано в этих статьях:

Исходные коды примера

Шаблон для создания своих плагинов: https://github.com/leonidv/vp-plugin-template 

Исходные коды плагина из статьи: https://github.com/leonidv/vp-plugin-er-comments 

Ссылки

Нельзя сказать, что материал создания плагинов для Visual Paradigm хорошо раскрыт в официальной документации. Тем не менее, для дальнейшего изучения будут полезны следующие ссылки:

Заключение

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

Сообщество пользователей Visual Paradigm

Visual Paradigm достаточно широкого используется в компаниях Российской Федерации в совершенно разных областях: стартапах, крупном бизнесе, госорганах. При этом как такового сообщества пользователей не сформировано. Я предлагаю всем заинтересованным присоединиться к группе в телеграмме https://t.me/visualparadigm_ru для обмена опытом. Группа только появилась и начинает развиваться. Присоединяйтесь, в группе будут появляться полезные материалы по работе с Visual Paradigm. 


ссылка на оригинал статьи https://habr.com/ru/post/685046/