Building a plugin

This document describes how to build a plugin for Modeling Tool. It will explain this by building a simple document source plugin. Document sources is an API in Modeling Tool that lets you build a connection to a document management system and expose the system's documents to Modeling Tool. Users can then use these documents the same way they would use any other document in Modeling Tool.

Prerequisites

Before starting you should probably know how Maven and OSGi works, at least on a surface level. OSGi is briefly discussed here.

Maven is primarily a system to handle dependencies between artifacts (such as jar-files). It is possible to build a plugin using Gradle or Ant but Maven is recommended as it is what 2c8 can help you with (and the system that this document assumes you're using). Maven is also able to build Java artifacts, deploy them and even build documentation sites (like this one!). The important part to know is that Maven uses an XML file called the pom.xml and a certain directory structure to find resources and java source files. The pom.xml file is where you state your dependencies and describe what you want to build and how to build it. The pom.xml file will thus describe to Maven how to build a 2c8 plugin (or really, how to build an OSGi bundle).

Exactly what OSGi and Maven is, is outside the scope of this document but there are several good resources available online.

Setup

To build a plugin, you first need a few things. Once you've setup your development environment and downloaded the dependencies, you can go to the next section.

  1. This guide assumes you're using IntelliJ IDEA, but you can use any text editing tool or IDE you like, for example Netbeans, Eclipse, vim, emacs and so on.
  2. A copy of the OpenJDK. Modeling Tool uses Java 17 but any OpenJDK since 17 and onwards should work. There are multiple vendors building the OpenJDK and you can chose any one of them. Here are some popular examples.
    1. AdoptOpenJDK This is the JDK used by Modeling Tool. Built by an organization mainly controlled by IBM.
    2. Red Hat OpendJDK built by RedHat.
    3. Azul OpenJDK built by Azul.
    4. OpenJDK Community This is built by the OpenJDK community.
  3. Modeling Tool is required to test and deploy your plugin. To get a license for development you can either use a demo license available at 2c8.com or you can contact our team at info@2c8.com
  4. Apache Maven, available from Maven's site. If you use a Java IDE, maven is usually included and no separate installation is necessary.
  5. Dependencies supplied by 2c8. Exactly which dependencies you need depends on the type of plugin you want to build.
    1. Extension Framework This is needed if you plan to interact with Modeling Tool. You can of course create OSGi bundles that don't interact with Modeling Tool, such as a plugin measuring the JVM heap space, or maybe a plugin using Java Swing to show a simple UI to send and receive messages from Slack or WhatsApp.
    2. Modeling Tool UI Needed if you plan to use custom controls not available in Java Swing. Most plugins will need this to get easy support for HiDPI enabled icons and when using custom JList/JTable/JTree renderers.
    3. Modeling Tool Look And Feel Needed if you're using the dependency above.
    4. Tools Needed if you're using any of the dependencies above.

Installing dependencies in Maven.

To be able to use the dependencies downloaded in the previous section you need to "install" them into your local Maven repository. Maven keeps a "repository" of all artifacts used in a designated directory. Where this directory is depends on your system but you typically don't want to handle the repository manually. Instead, you can put the dependencies into the registry by invoking certain Maven commands from your favourite command interpreter.

Once Maven is installed (either as part of your IDE or as a standalone installation) you should be able to type

mvn --version

in a command line interpreter.

If that doesn't work, you need to locate the path to your Maven installation and setup your command line interpreter so that it can find the Maven executables (setting the PATH environment variable on Windows for example).

Once you've done this, install the dependencies you downloaded in the previous section with the following commands:

mvn install:install-file -DgroupId=se.conciliate -DartifactId=ExtensionFrameworkMaven -Dversion=2024.1.5 -Dpackaging=jar -Dfile=<PATH_TO_FILE>
mvn install:install-file -DgroupId=se.conciliate -DartifactId=ui -Dversion=3.6 -Dpackaging=jar -Dfile=<PATH_TO_FILE>
mvn install:install-file -DgroupId=se.conciliate -DartifactId=modeling-tool-ui-laf -Dversion=3.3 -Dpackaging=jar -Dfile=<PATH_TO_FILE>
mvn install:install-file -DgroupId=se.conciliate -DartifactId=tools -Dversion=2.18 -Dpackaging=jar -Dfile=<PATH_TO_FILE>

If you have used Maven before you might be wondering why you need to download and install dependencies manually. This is because the dependencies listed above are not available from Maven Central or any of the other popular Maven repositories. They may be in the future, but currently, they're distributed this way.

Building an example plugin

Building a plugin requires knowledge about how OSGi bundles are structured and how to achieve that structure with Maven. To make things easier, we've created a simple plugin that implements DocumentSource. Document sources are external systems that knows how to fetch meta data about documents (such as document title, icon and url). Modeling Tool can then display that meta data to users and let users connect the documents to models and objects in Modeling Tool.

The example plugin serves as a good starting point to building your own plugin. Start by downloading the example code for the Disk Drive Document Source. This implementation doesn't really connect to a document management system but rather uses the user's local disk drive to locate documents.

Unzip the file and place the contents anywhere on your system. Now start your favourite IDE and open the project (most IDEs will let you open the project as a Maven project if you either select the pom.xml file or the folder where you extracted the contents of the zip file).

Build the project. If you're using a command line interpreter for this (rather than using an IDE), this can be done by going to the root of the project (the directory containing the pom.xml file) and executing the command mvn install or mvn clean install for a "clean and build".

This creates a directory called /target in the project directory (a sibling of the pom.xml file). Open this directory and locate the file disk-drive-documents-1.0-SNAPSHOT.jar. Copy the file to your C:\Users\_youruser_\AppData\Local\2conciliate\2024.1\ext directory. As explained in this document, Modeling Tool scans this directory for new bundles to install.

Now, start Modeling Tool (if it isn't already started) and open up a repository. The plugin should be installed and show a settings tab in the repository. To make sure it is installed you can go to Tools -> Settings -> Plug-in and confirm that there is an entry for "Disk drive documents".

The plugin simply reads files from your hard drive and makes them available to Modeling Tool. However, since this is an example plugin it isn't all that refined. In particular, it does not list all files on the disk, just files in directories that the user selects in the settings tab for the plugin. This settings tab should be available once a repository is opened in Modeling Tool. Click the add button to add a new directory, then click the button "Save settings" to save and close the settings tab (this tab is not in the Tools -> Settings dialog but rather directly in the modeling area). All files in the selected directory (and any child directories) are now available for Modeling Tool to use. You can see this by clicking the "Documents" icon in the sidebar. DiskDriveDocuments is a plugin that isn't written with large directory structures in mind (it is an example plugin after all) so don't pick a directory with too many files.

If you've gotten this far, it is time to open up the project files and look at the comments. Start with DiskDriveDocumentSource.java. Many methods here explain what they are doing and why. For information about the interface that DiskDriveDocumentSource implements, have a look at DocumentSource.

You can start to make changes to the document source and its meta data descriptors and rebuild the project using mvn clean install. Then, copy the resulting jar-file from the /target directory to the ext directory. This can be done while Modeling Tool is running. It will automatically update the plugin and your changes should be visible within two seconds (the number of seconds between scans of the ext directory).

Also familiarise yourself with the project's pom.xml (located in the root of the project) and the OSGI-INF/service-declaration.xml. The former describes how Maven will build your plugin and how Maven creates an OSGi bundle, while the latter describes your OSGi service. The service declaration is briefly documented while the pom.xml file has very little documentation. The important parts of the pom file is briefly described in following sections.

First, lets cover how to setup auto-deployment so that you don't have to manually copy your built jar-file from the /target directory to the /ext directory.

Setting up auto-deployment

Copying the file from /target to /ext every time you rebuild your project quickly becomes time consuming. To avoid this, you can setup your pom.xml and settings.xml files to automatically copy the file from /target to /ext. First, open your pom file and locate the plugins element (a child of the build element). If you're using the example project from previous sections, you should be able to find a plugin element looking like this:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.8</version>
    <executions>
        <execution>
            <id>installMyBundle</id>
            <phase>package</phase>
            <configuration>
                <tasks>
                    <copy
                            file="${project.build.directory}/${project.artifactId}-${project.version}.jar"
                            todir="${user.plugins}" />
                </tasks>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

If you're building your plugin from scratch, add this plugin element to the plugins element. This element instructs maven to run an Ant task (Ant is another build system but can be invoked from Maven). The ant task will copy a file from whatever is in the "file" attribute to the directory specified by the "todir" attribute of the copy element. As you can see, the paths are using variables rather than specifying them directly. This is because pom files are often committed to VCSs (git, svn, mercurial) but the paths to copy from and to will differ depending on the developer's environment. So, to avoid this part being changed on every commit and every developer working on the project needing to change it after each update (pull, fetch), variables are used. These variables are then defined in a settings.xml.

The settings.xml is located in your .m2 directory. This is mavens directory where it keeps your local Maven repository. On Windows the .m2 directory is located at C:\Users\_youruser_\.m2. Create a settings file here containing at least the following, where <user.plugins> should contain a path to your Modeling Tool version's /ext directory. The example is assuming you're running Windows 10. On other systems, the ext directory will be located elsewhere.

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <profiles>
        <profile>
            <id>build-all</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <user.plugins>${user.home}/AppData/Local/2conciliate/2024.1/ext</user.plugins>
            </properties>
        </profile>
    </profiles>
    <activeProfiles>
        <activeProfile>build-all</activeProfile>
    </activeProfiles>
</settings>

Once this is setup, try building your project. If everything works, your bundle jar-file should be automatically copied from the /target directory to the /ext directory and Modeling Tool will take care of automatically deploying (or re-deploying) the plugin.

Adding Imports/Exports to other packages

This section describes how to use packages other than those specified in the example project disk-drive-documents.

Import and export instructions

If you're building your plugins using this guide, you have used Maven to build OSGi bundles. This is accomplished with a Maven plugin called maven-bundle-plugin. This Maven plugin creates an OSGi manifest for you so that you don't have to manually create it everytime you rebuild your project. The plugin has a fairly comprehensive documentation here. In your pom.xml, you can find the instructions that are relevant for building OSGi bundles. Open the pom.xml and search for maven-bundle-plugin. Then look inside that element for a descendant called instructions. This is where you instruct maven-bundle-plugin how to build an OSGi manifest.

In the example project you can see that your bundle will try to import the following packages.

se.conciliate.extensions.documents;version="[1.0, 2.0)",
se.conciliate.mt.repository.session;version="[1.0, 2.0)",
net.miginfocom.swing;version="[4.0, 5.0)"

This means that your bundle requires some other OSGi bundle to export these packages and versions. You may also notice the * under the import package instruction. This is a wild card. Once your classes are compiled, maven-bundle-plugin will scan your class files (not the source files) and see which packages you are referencing. All packages you're referencing will end up as imports in the resulting OSGi manifest if you're using the wild card. If you're not using the wild card, only the explicitly stated packages will end up as imports in the resulting manifest. Now, if your bundle tries to load a class from a package that is not on the list of imported packages, you will get a ClassNotFoundError when running your code. It is therefore important to always specify all packages that you need. This is why the wild card comes in handy.

Wild cards can be used in other places too. If you want to import all packages starting with se.conciliate, you can type se.conciliate.*. Typing se.conciliate* will also import all packages starting with se.conciliate, but will also import packages such as se.conciliateABC.

Note: When using wild cards, only packages found during the scanning of your class files will be added to the import list. Using explicit package names will include the package in the import list even if they're not found during the scan. This can be useful if you reference classes using reflection and you dynamically construct the class name lookups. (Don't use Class#forName in OSGi environments.)

Unless you want to provide packages for other bundles to use, the export package instruction can be left blank.

Private package instruction

By default, maven-bundle-plugin will only add packages from your sources to the resulting bundle jar file that you have specified as a private package. This means that when you create new packages in your source directory, you need to add them as a private package. Otherwise the package will not be present in the jar file and your bundle will be unable to start (because maven-bundle-plugin will automatically try to import the package and since no other bundle exports the package, bundle resolution will fail).

Adding third party libraries

You can add third party libraries to your bundle by specifying them as normal Maven dependencies. If you look at the pom.xml of the example project you will see that it has a declared dependency on javax.json. You can find this under the element dependencies and one of its child elements (search the file for "javax.json" if you're unable to find it). Notice how it has a scope element specifying a compile time scope. This tells Maven that the dependency is needed both at compile time and at runtime, but that the runtime will not provide the dependency. Notice also how other dependencies have a scope of provided. This tells Maven that the dependency is needed for compilation, but that the runtime will provide the dependency. For compile-scoped dependencies, maven-bundle-plugin will include the dependency inside your bundle jar and at runtime, any classes that your bundle needs will be loaded from there. Thus, in the example pom.xml, classes in javax.json will be available to your bundle.

To add other libraries, just add a normal Maven dependency to it and set the scope to compile.

Note: If you add dependencies where you need to send instances of classes from the dependency to Modeling Tool, you need to make sure that Modeling Tool is using the same class loader to load the class as your bundle does. Each bundle gets its own class loader so unless you're getting the class by importing the the package, chances are that Modeling Tool and your bundle will use different class loaders to load the class. When this happens you will get a ClassCastException. All this means is: Never use scope compile for dependencies provided by Modeling Tool, such as ExtensionFrameworkMaven, modeling-tool-ui or tools. These are provided by the runtime and if you embed them into your bundle (by setting the scope to compile), your bundle's class loader will load the class and this will be considered a different class from the one that Modeling Tool loaded (due to how Java class equality works) and you will get a ClassCastException.

How to Debug your plugin (attach a debugger) from IntelliJ.

To debug your plugin using IntelliJ, you must start Modeling Tool in debug mode. This is done by locating your Modeling Tool installation directory. On Windows 10 this is typically located at C:\Program Files\2conciliate\2c8 Modeling Tool 2024.1 (depending on version). Locate the directory in a command line interpreter and type

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=9009 -jar ModelingTool.jar

This will start Modeling Tool in debug mode. Note though that the application will not start until you have attached a debugger. If you get a permission error, try a different port (address=9009, set 9009 to some other number that corresponds to an unused port on your machine). If you keep getting permission errors, consult your firewall manual.

To attach a debugger in IntelliJ, go to the menu Run and pick "Attach to process...". Your Modeling Tool instance should be available there. If it isn't, you can instead edit your run configurations by going to the menu Run and picking "Edit configurations...". In the dialog that pops up, press the plus icon and pick "Remote JVM Debug". Give your configuration a name and set the port to 9009. Press OK and then select your configuration from the build configurations drop down in IntelliJ and press the debug button.