26 Commits

Author SHA1 Message Date
85ad7cdded v1.5.x 2025-04-08 17:06:21 -04:00
36aa216a59 fixed jaxb/jakarta runtime issue 2025-04-08 17:05:34 -04:00
ee0f0b4824 added DecisionTable support 2025-04-08 15:25:46 -04:00
04d26fb251 publish failures result in 200 code; fudging to 412 code 2024-03-13 10:41:21 -04:00
9b4287b811 only set OAuth client secret or password if set 2024-03-13 10:35:01 -04:00
8d634670e1 added support for encrypted server passwords 2024-01-23 09:06:17 -05:00
a856f9580b changed template naming 2023-11-16 13:39:58 -05:00
0b9473465e mapping model not just ID 2023-11-16 09:19:57 -05:00
a65a6df1f8 added dryRun for app upload/publish 2023-11-15 20:00:58 -05:00
98c01db50d fixed template handling 2023-11-15 20:00:45 -05:00
b974bc8f5c sort_by_name_asc 2023-11-15 18:04:50 -05:00
8bb15894e8 updated javadocs 2023-11-15 17:01:39 -05:00
bd5b218509 added initial template support 2023-11-15 16:28:21 -05:00
55d85814ab updated APS model/client libs 2023-07-07 15:22:59 -04:00
78b7fc2ec9 updated to latest APS API/client 2023-05-28 13:34:10 -04:00
63ffd77622 fixed NPE on expressions 2022-11-18 15:40:50 -05:00
4d064916e9 fixed multiple condition translation in process model JSON 2022-11-18 14:01:46 -05:00
c441a61d37 added README 2022-10-10 23:44:14 -04:00
8eae54f753 moved @see javadoc 2022-10-10 22:28:27 -04:00
86d4d76e54 fixed javadocs 2022-10-10 22:25:47 -04:00
cc1019a22e added normalizeFile() method 2022-10-10 22:17:52 -04:00
330eab9cf1 moved modelVersion from translator to normalizer 2022-10-10 22:17:25 -04:00
70506bb040 index null validation 2022-10-10 22:16:53 -04:00
cc973b0a28 refactored sorters 2022-10-10 22:16:34 -04:00
99d76df708 added docs; logging; prep for maven central 2022-10-10 22:14:02 -04:00
dd9d5735bf fixed user-based translation 2022-06-03 13:53:05 +01:00
54 changed files with 3566 additions and 409 deletions

3
.gitignore vendored
View File

@@ -7,3 +7,6 @@ pom.xml.versionsBackup
.project
.classpath
# Codegen
.apt_generated
.apt_generated_tests

348
README.md Normal file
View File

@@ -0,0 +1,348 @@
# APS Model Maven Plugin
This is a maven plugin that APS model management. The use cases include synchronization between disparate APS instances, like the progressive environments you find in most enterprise infrastructures. It will also synchronize APS instances to Git or Git to APS instances.
## Usage
Here is an example configuration for synchronizing an APS instance to the local project so it can be committed to Git.
```xml
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>...</version>
<configuration>
<activitiAppBaseUrl>http://localhost:8080/activiti-app</activitiAppBaseUrl>
<activitiAppAuthBasicServerId>aps</activitiAppAuthBasicServerId>
<apsAppName>Test App</apsAppName>
</configuration>
<executions>
<execution>
<id>download-unpack-app</id>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<normalize>true</normalize>
<diffFriendly>true</diffFriendly>
</configuration>
</execution>
<execution>
<id>translate-app</id>
<phase>compile</phase>
<goals>
<goal>translate-app</goal>
</goals>
<configuration>
<finalDirectory>${basedir}/src/main/aps/app</finalDirectory>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
...
</project>
```
For `BASIC` authentication, you will need to define a `server` in the `settings.xml` as shown in the snippet below.
```xml
<settings ...>
...
<servers>
...
<server>
<id>aps</id>
<username>...</username>
<password>...</password>
</server>
...
</servers>
...
</settings>
```
Here is an example configuration for synchronizing the local project to an APS instance.
```xml
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>...</version>
<configuration>
<activitiAppBaseUrl>http://localhost:8080/activiti-app</activitiAppBaseUrl>
<activitiAppAuthBasicServerId>aps</activitiAppAuthBasicServerId>
<apsAppName>Test App</apsAppName>
</configuration>
<executions>
<execution>
<id>translate-app</id>
<phase>compile</phase>
<goals>
<goal>translate-app</goal>
</goals>
<configuration>
<unzipDirectory>${basedir}/src/main/aps/app</unzipDirectory>
</configuration>
</execution>
<execution>
<id>pack-app</id>
<phase>package</phase>
<goals>
<goal>pack-app</goal>
</goals>
</execution>
<execution>
<id>upload-app</id>
<phase>install</phase>
<goals>
<goal>upload-app</goal>
</goals>
</execution>
<execution>
<id>publish-app</id>
<phase>deploy</phase>
<goals>
<goal>publish-app</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
...
</project>
```
Here is an example configuration for synchronizing directly from a source to different target APS instance.
```xml
<project>
...
<build>
...
<plugins>
...
<plugin>
<groupId>com.inteligr8</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>...</version>
<configuration>
<apsAppName>Test App</apsAppName>
</configuration>
<executions>
<execution>
<id>download-unpack-app</id>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<activitiAppBaseUrl>http://dev-host:8080/activiti-app</activitiAppBaseUrl>
<activitiAppAuthBasicServerId>aps-dev</activitiAppAuthBasicServerId>
<normalize>true</normalize>
<diffFriendly>true</diffFriendly>
</configuration>
</execution>
<execution>
<id>translate-app</id>
<phase>compile</phase>
<goals>
<goal>translate-app</goal>
</goals>
<configuration>
<activitiAppBaseUrl>http://test-host:8080/activiti-app</activitiAppBaseUrl>
<activitiAppAuthBasicServerId>aps-test</activitiAppAuthBasicServerId>
</configuration>
</execution>
<execution>
<id>pack-app</id>
<phase>package</phase>
<goals>
<goal>pack-app</goal>
</goals>
</execution>
<execution>
<id>upload-publish-app</id>
<phase>deploy</phase>
<goals>
<goal>upload-app</goal>
<goal>publish-app</goal>
</goals>
<configuration>
<activitiAppBaseUrl>http://test-host:8080/activiti-app</activitiAppBaseUrl>
<activitiAppAuthBasicServerId>aps-test</activitiAppAuthBasicServerId>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
...
</project>
```
You will want the Maven project to be of packaging type `pom`.
## Goals
| Goal | Description |
| --------------- | ----------- |
| `aps-info` | Check the connection to APS and output version information. |
| `download-app` | Export and download an APS App as a ZIP file from an APS instance. The latest published version is exported. |
| `upload-app` | Upload and import an APS App from a ZIP file to an APS instance. The APS App is created or updated, but not published. |
| `unpack-app` | Unzip the ZIP file exported from the APS instance. |
| `pack-app` | Zip a previously unpacked APS App in preparation for import to an APS instance. |
| `publish-app` | Publish an existing APS App in an APS instance. |
| `translate-app` | Translate an unpacked APS App. |
| `share-models` | Share applicable models in an APS instance. |
### Goal: `aps-info`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |
### Goal: `download-app`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `zipDirectory` | `string` | | `target/aps` | The base directory where the ZIP file should be downloaded. |
| `apsAppName` | `string` | Yes | | An APS App name that exists in the APS instance. |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |
### Goal: `unpack-app`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `apsAppName` | `string` | Yes | | The name of the APS App ZIP file, without the `.zip` extension. |
| `zipDirectory` | `string` | | `target/aps` | The base directory where the ZIP file should exist. |
| `unzipDirectory` | `string` | | `target/aps/app` | The base directory where the ZIP file should be unpacked; the APS App name will be another folder within this directory. |
| `reformat` | `boolean` | | `true` | The JSON and XML files should be reformatted for readability and better `diff`/Git support. |
| `normalize` | `boolean` | Yes | | The JSON and XML files should be normalized for better `diff`/Git support. |
| `diffFriendly` | `boolean` | Yes | | The JSON file models should be sorted for better `diff`/Git support. |
| `charsetName` | `string` | | `utf-8` | |
| `skip` | `boolean` | | `false` | |
### Goal: `translate-app`
This goal translates an APS App configuration so all the model and organization IDs reference the specified APS instance. This is the most important functionality provied by this Maven plugin. It allows for the clean synchronization of APS models between environments; forward and backwards.
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `apsAppName` | `string` | Yes | | The name of the folder in the `unzipDirectory`. |
| `unzipDirectory` | `string` | | `target/aps/app` | The base directory where the unpacked APS App exists. |
| `finalDirectory` | `string` | | `target/aps/app` | The base directory where the translated APS App should exist. If it is the same as `unzipDirectory`, then it is translated in-place. |
| `overwrite` | `boolean` | | `true` | |
| `charsetName` | `string` | | `utf-8` | |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |
### Goal: `pack-app`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `apsAppName` | `string` | Yes | | The name of the folder in the `unzipDirectory`. |
| `unzipDirectory` | `string` | | `target/aps/app` | The base directory where the unpacked APS App is located; the APS App name will be a folder within this directory. |
| `zipDirectory` | `string` | | `target/aps` | The base directory where the new ZIP file should exist. |
| `skip` | `boolean` | | `false` | |
### Goal: `upload-app`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `zipDirectory` | `string` | | `target/aps` | The base directory where the ZIP file should be downloaded. |
| `apsAppName` | `string` | Yes | | An APS App name that exists in the APS instance. |
| `publish` | `boolean` | | `false` | Automatically publish after upload. |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |
### Goal: `publish-app`
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `apsAppName` | `string` | Yes | | An APS App name that exists in the APS instance. |
| `comment` | `string` | | `Automated by 'aps-model-maven-plugin'` | A comment to apply to the new version of the APS App. |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |
### Goal: `share-models`
This goal queries the APS instance for all applicable models and shares them based on the configuration. This is very useful in highly collaborative environments. It may be worth having this run on a routine basis in the development environment and on every synchronization to other environments.
| Configuration Property | Data Type | Required | Default | Description |
| ------------------------------ |:---------:|:--------:| ------- | ----------- |
| `modelName` | `string` | | | A single model to share. If the same name is used for two different model types (app and process), then both are shared. If this is left unspecified, all models are shared. |
| `readers` | `string` | | | A comma-delimited list of organizations/groups to target for readonly access to all model types. |
| `editors` | `string` | | | A comma-delimited list of organizations/groups to target for editor access to all model types. |
| `appReaders` | `string` | | | A comma-delimited list of organizations/groups to target for readonly access to only APS App models. This is needed for APS App publishers. |
| `appEditors` | `string` | | | A comma-delimited list of organizations/groups to target for editor access to only APS App models. This is needed for APS App collators. |
| `processReaders` | `string` | | | A comma-delimited list of organizations/groups to target for readonly access to only APS Process models. This is needed for APS App collators. |
| `processEditors` | `string` | | | A comma-delimited list of organizations/groups to target for editor access to only APS Process models. This is needed for APS Process modelers. |
| `formReaders` | `string` | | | A comma-delimited list of organizations/groups to target for readonly access to only APS Form models. This is needed for APS Process modelers. |
| `formEditors` | `string` | | | A comma-delimited list of organizations/groups to target for editor access to only APS Form models. This is needed for APS Form designers. |
| `doRevoke` | `boolean` | | `false` | If `true`, revoke permission to any other groups/users for the applicable models; `false` will leave those permissions intact. |
| `activitiAppBaseUrl` | `string` | | `http://localhost:8080/activiti-app` | The base URL of the APS instance. |
| `activitiAppAuthType` | `string` | | `BASIC` | Either `BASIC` or `OAuth`; case insensitive. |
| `activitiAppAuthBasicServerId` | `string` | | `aps` | The `settings.xml` server ID that provides the credentials for the APS instance when using `BASIC` authentication. |
| `oauthCode` | `string` | *Maybe* | | An authorization code for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthClientServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the client ID/secret credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthServerId` | `string` | *Maybe* | | The `settings.xml` server ID that provides the username/password credentials for the identity provider governing the APS instance when using `OAuth` authentication. |
| `oauthTokenUrl` | `string` | *Maybe* | | The token URL for the identity provider governing the APS instance when using `OAuth` authentication. |
| `skip` | `boolean` | | `false` | |

201
pom.xml
View File

@@ -1,18 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project 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/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>1.3-SNAPSHOT</version>
<version>1.5-SNAPSHOT</version>
<packaging>maven-plugin</packaging>
<name>A Maven plugin for Alfresco Process Services model portability</name>
<description>Generate Alfresco Module Packages (AMP) files in a way simialr to WAR files</description>
<url>https://bitbucket.org/inteligr8/amp-maven-plugin</url>
<licenses>
<license>
<name>GNU GENERAL PUBLIC LICENSE, Version 3, 29 June 2007</name>
<url>https://www.gnu.org/licenses/lgpl-3.0.txt</url>
</license>
</licenses>
<scm>
<connection>scm:git:https://bitbucket.org/inteligr8/aps-model-maven-plugin.git</connection>
<developerConnection>scm:git:git@bitbucket.org:inteligr8/aps-model-maven-plugin.git</developerConnection>
<url>https://bitbucket.org/inteligr8/aps-model-maven-plugin</url>
</scm>
<organization>
@@ -32,28 +42,26 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.version>3.6.3</maven.version>
<maven.version>3.9.9</maven.version>
<jersey.version>2.35</jersey.version>
<jersey.version>3.1.10</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-api</artifactId>
<version>2.0.1</version>
<classifier>aps1</classifier>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-client</artifactId>
<version>2.0.1</version>
<classifier>jersey</classifier>
<version>3.0.2-jersey</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
@@ -68,7 +76,7 @@
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>file-management</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
@@ -79,7 +87,7 @@
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.0</version>
<version>3.15.1</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -103,16 +111,42 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.9.0</version>
<configuration>
<projectsDirectory>${basedir}/src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
<localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<mavenHome>${env.MAVEN_HOME}</mavenHome>
<debug>true</debug>
<skipInvocation>${skipTests}</skipInvocation>
<properties>
<project.main.basedir>${basedir}</project.main.basedir>
<aps-model.baseUrl>${aps-model.baseUrl}</aps-model.baseUrl>
<aps-model.authType>${aps-model.authType}</aps-model.authType>
<aps-model.basicAuth.mavenServerId>${aps-model.basicAuth.mavenServerId}</aps-model.basicAuth.mavenServerId>
<aps-model.appName>${aps-model.appName}</aps-model.appName>
<aps-model.share.editors>${aps-model.share.editors}</aps-model.share.editors>
<aps-model.share.readers>${aps-model.share.readers}</aps-model.share.readers>
<aps-model.share.app.editors>${aps-model.share.app.editors}</aps-model.share.app.editors>
</properties>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
<version>3.15.1</version>
<configuration>
<goalPrefix>aps-model</goalPrefix>
</configuration>
@@ -134,7 +168,7 @@
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
<version>2.0.0</version>
<version>2.2.0</version>
<executions>
<execution>
<goals>
@@ -143,55 +177,29 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<projectsDirectory>${basedir}/src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
<localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<mavenHome>${env.MAVEN_HOME}</mavenHome>
<debug>true</debug>
<skipInvocation>${skipTests}</skipInvocation>
<properties>
<project.main.basedir>${basedir}</project.main.basedir>
<aps-model.baseUrl>${aps-model.baseUrl}</aps-model.baseUrl>
<aps-model.authType>${aps-model.authType}</aps-model.authType>
<aps-model.basicAuth.mavenServerId>${aps-model.basicAuth.mavenServerId}</aps-model.basicAuth.mavenServerId>
<aps-model.appName>${aps-model.appName}</aps-model.appName>
<aps-model.share.editors>${aps-model.share.editors}</aps-model.share.editors>
<aps-model.share.readers>${aps-model.share.readers}</aps-model.share.readers>
<aps-model.share.app.editors>${aps-model.share.app.editors}</aps-model.share.app.editors>
</properties>
</configuration>
<executions>
<execution>
<id>run-its</id>
<goals>
<goal>install</goal>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>javadoc</id>
<phase>package</phase>
<goals><goal>jar</goal></goals>
<configuration>
<show>public</show>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>run-its</id>
<build>
<plugins>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<executions>
<execution>
<id>run-its</id>
<goals>
<goal>install</goal>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>run-it-1</id>
<activation>
@@ -203,10 +211,9 @@
<plugins>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<id>run-its</id>
<id>run-it-1</id>
<goals>
<goal>install</goal>
<goal>run</goal>
@@ -222,22 +229,56 @@
</plugins>
</build>
</profile>
<profile>
<id>ossrh-release</id>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>source</id>
<phase>package</phase>
<goals><goal>jar-no-fork</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<executions>
<execution>
<id>javadoc</id>
<phase>package</phase>
<goals><goal>jar</goal></goals>
<configuration>
<show>public</show>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign</id>
<phase>verify</phase>
<goals><goal>sign</goal></goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>inteligr8-releases</id>
<name>Inteligr8 Releases</name>
<url>https://repos.inteligr8.com/nexus/repository/inteligr8-public</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>inteligr8-releases</id>
<name>Inteligr8 Releases</name>
<url>https://repos.inteligr8.com/nexus/repository/inteligr8-public</url>
</repository>
</distributionManagement>
</project>

View File

@@ -1,13 +1,53 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.crawler;
/**
* An interface for APS App export transformation implementations.
*
* An APS App export is a ZIP file that always contains an APS App JSON file.
* It may also contain many process XML/JSON files, form JSON files, thumbnail
* images, and more.
*
* @author brian@inteligr8.com
*/
public interface ApsAppCrawlable {
/**
* @return A file transformer for APS App JSON files.
*/
ApsFileTransformer getAppJsonTransformer();
/**
* @return A file transformer for APS Process JSON files.
*/
ApsFileTransformer getProcessJsonTransformer();
/**
* @return A file transformer for APS Process BPMN (XML) files.
*/
ApsFileTransformer getProcessBpmnTransformer();
/**
* @return A file transformer for APS Form JSON files.
*/
ApsFileTransformer getFormJsonTransformer();
/**
* @return A file transformer for APS Decision Table JSON files.
*/
ApsFileTransformer getDecisionTableJsonTransformer();
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.crawler;
import java.io.File;
@@ -11,6 +25,12 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that implements a APS App export crawler. The crawler does not
* directly perform any transformations. Those are handled through a callback.
*
* @author brian@inteligr8.com
*/
public class ApsAppCrawler {
private final Logger logger = LoggerFactory.getLogger(ApsAppCrawler.class);
@@ -20,27 +40,39 @@ public class ApsAppCrawler {
private final File appDescriptor;
private final boolean failOnIntegrityViolation;
/**
* @param apsAppName A name for the APS App.
* @param apsAppDirectory A directory to the unpacked APS App export.
* @param failOnIntegrityViolation true to fail on file integrity issues; false to log warnings.
*/
public ApsAppCrawler(String apsAppName, File apsAppDirectory, boolean failOnIntegrityViolation) {
this.appName = apsAppName;
this.appDirectory = apsAppDirectory;
this.failOnIntegrityViolation = failOnIntegrityViolation;
this.appDescriptor = this.validateDescriptor(appDirectory);
this.appDescriptor = this.validateDescriptor();
if (this.logger.isDebugEnabled())
this.logger.debug("APS App descriptor found: " + this.appDescriptor);
}
protected File validateDescriptor(File appDirectory) {
File appDescriptor = new File(appDirectory, this.appName + ".json");
/**
* @return A file handle to the root APS App JSON (configuration) file.
*/
protected File validateDescriptor() {
File appDescriptor = new File(this.appDirectory, this.appName + ".json");
if (!appDescriptor.exists())
throw new IllegalStateException("The APS App descriptor could not be found: " + appDescriptor);
throw new IllegalStateException("The APS App descriptor could not be found: " + this.appDescriptor);
if (!appDescriptor.isFile())
throw new IllegalStateException("The APS App descriptor is not a file: " + appDescriptor);
throw new IllegalStateException("The APS App descriptor is not a file: " + this.appDescriptor);
return appDescriptor;
}
/**
* @param crawlable A crawlable implementation; the callback for potential transformations.
* @throws IOException A file access exception occurred.
*/
public void execute(ApsAppCrawlable crawlable) throws IOException {
this.logger.info("Crawling APS App ...");
@@ -51,6 +83,7 @@ public class ApsAppCrawler {
this.transform(crawlable.getAppJsonTransformer(), this.appDescriptor, this.appName, null);
this.crawlModels("form-models", crawlable.getFormJsonTransformer());
this.crawlModels("decision-table-models", crawlable.getDecisionTableJsonTransformer());
this.crawlModels("bpmn-models", processTransformers);
this.crawlModels("bpmn-subprocess-models", crawlable.getProcessJsonTransformer());
}
@@ -83,7 +116,7 @@ public class ApsAppCrawler {
}
String modelName = matcher.group(1);
Long modelId = new Long(matcher.group(2));
Long modelId = Long.valueOf(matcher.group(2));
this.logger.trace("Transforming model {} ID: {}", modelName, modelId);
this.transform(transformer, modelFile, modelName, modelId);

View File

@@ -1,10 +1,39 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.crawler;
import java.io.File;
import java.io.IOException;
/**
* This interface provides a common method for transforming various files in an
* exported APS App.
*
* @author brian@inteligr8.com
*/
public interface ApsFileTransformer {
/**
* This method transforms the specified file which should be referred to
* with the specified name and ID.
*
* @param file A model file.
* @param modelName The target model name.
* @param modelId The target model ID.
* @throws IOException An I/O related exception has occurred during transformation.
*/
void transformFile(File file, String modelName, Long modelId) throws IOException;
}

View File

@@ -0,0 +1,31 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.crawler;
/**
* An interface for APS template export transformation implementations.
*
* An APS template is a JSON file.
*
* @author brian@inteligr8.com
*/
public interface ApsTemplateCrawlable {
/**
* @return A file transformer for APS template JSON files.
*/
ApsFileTransformer getTemplateJsonTransformer();
}

View File

@@ -0,0 +1,73 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.crawler;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A class that implements a APS App export crawler. The crawler does not
* directly perform any transformations. Those are handled through a callback.
*
* @author brian@inteligr8.com
*/
public class ApsTemplateCrawler {
private final Logger logger = LoggerFactory.getLogger(ApsTemplateCrawler.class);
private final File templateDirectory;
private final boolean failOnIntegrityViolation;
/**
* @param apsTemplateDirectory A directory to the APS template files.
* @param failOnIntegrityViolation true to fail on file integrity issues; false to log warnings.
*/
public ApsTemplateCrawler(File apsTemplateDirectory, boolean failOnIntegrityViolation) {
this.templateDirectory = apsTemplateDirectory;
this.failOnIntegrityViolation = failOnIntegrityViolation;
}
/**
* @param crawlable A crawlable implementation; the callback for potential transformations.
* @throws IOException A file access exception occurred.
*/
public void execute(ApsTemplateCrawlable crawlable) throws IOException {
this.logger.info("Crawling APS templates ...");
this.crawlTemplates(crawlable.getTemplateJsonTransformer());
}
protected void crawlTemplates(ApsFileTransformer transformer) throws IOException {
for (File templateFile : this.templateDirectory.listFiles()) {
if (!templateFile.getName().endsWith(".json"))
continue;
this.logger.trace("Transforming template: {}", templateFile);
try {
transformer.transformFile(templateFile, null, null);
} catch (IllegalArgumentException | IllegalStateException ie) {
if (this.failOnIntegrityViolation) {
throw ie;
} else {
this.logger.warn(ie.getMessage());
}
}
}
}
}

View File

@@ -1,13 +1,42 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.util.List;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import com.inteligr8.alfresco.activiti.ApsClientJerseyConfiguration;
import com.inteligr8.alfresco.activiti.ApsClientJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsProtectedRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.Tenant;
/**
* This class adds APS addressbility to extending goals.
*
* This is a common need for several goals in this library, as most of those
* goals will need to call an APS service to perform their work.
*
* @author brian@inteligr8.com
*/
public abstract class ApsAddressibleGoal extends DisablableGoal {
@Parameter( defaultValue = "${session}", readonly = true )
@@ -33,13 +62,25 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
@Parameter( property = "aps-model.oauth.tokenUrl", required = false )
protected String oauthTokenUrl;
@Component
private SettingsDecrypter decrypter;
private ApsPublicRestApiJerseyImpl api;
private ApsProtectedRestApiJerseyImpl api;
public ApsClientJerseyConfiguration getApsClientConfiguration() {
/**
* Retrieves an APS client configuration.
*
* The configuration is built based on the properties injected into this
* class. It supports either `BASIC` or `OAuth` based authentication.
*
* @return An APS client configuration.
*/
public ApsClientJerseyConfiguration getApsClientConfiguration() throws MojoExecutionException {
this.getLog().debug("Configuring APS to URL: " + this.activitiAppBaseUrl);
ApsClientJerseyConfiguration config = new ApsClientJerseyConfiguration();
config.setBaseUrl(this.activitiAppBaseUrl);
switch (this.activitiAppAuthType.toUpperCase()) {
case "BASIC":
this.getLog().info("Configuring APS with BASIC authentication");
@@ -49,6 +90,9 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
this.getLog().warn("The Maven configuration has no server '" + this.activitiAppAuthBasicServerId + "'; continuing with default credentials");
if (creds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(creds))
.getServer();
this.getLog().debug("Username: " + creds.getUsername());
config.setBasicAuthUsername(creds.getUsername());
config.setBasicAuthPassword(creds.getPassword());
@@ -62,19 +106,29 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
if ((this.oauthClientServerId != null || this.oauthServerId != null) && clientCreds == null && oauthCreds == null)
this.getLog().warn("The Maven configuration has no server '" + this.oauthClientServerId + "' or '" + this.oauthServerId + "'; continuing without credentials");
this.getLog().debug("OAuth Code: " + this.oauthCode);
config.setOAuthAuthCode(this.oauthCode);
if (this.oauthCode != null) {
this.getLog().debug("OAuth Code: " + this.oauthCode);
config.setOAuthAuthCode(this.oauthCode);
}
if (clientCreds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(clientCreds))
.getServer();
this.getLog().debug("OAuth Client ID: " + clientCreds.getUsername());
config.setOAuthClientId(clientCreds.getUsername());
config.setOAuthClientSecret(clientCreds.getPassword());
if (clientCreds.getPassword() != null && clientCreds.getPassword().length() > 0)
config.setOAuthClientSecret(clientCreds.getPassword());
}
if (oauthCreds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(oauthCreds))
.getServer();
this.getLog().debug("OAuth Username: " + oauthCreds.getUsername());
config.setOAuthUsername(oauthCreds.getUsername());
config.setOAuthPassword(oauthCreds.getPassword());
if (oauthCreds.getPassword() != null && oauthCreds.getPassword().length() > 0)
config.setOAuthPassword(oauthCreds.getPassword());
}
config.setOAuthTokenUrl(this.oauthTokenUrl);
@@ -86,14 +140,36 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
return config;
}
public synchronized ApsPublicRestApiJerseyImpl getApsApi() {
/**
* This method constructs and caches the APS API accessor.
*
* @return An APS API instance.
* @throws MojoExecutionException The APS API failed to initialize.
*/
public synchronized ApsProtectedRestApiJerseyImpl getApsApi() throws MojoExecutionException {
if (this.api == null) {
ApsClientJerseyConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config);
this.api = new ApsPublicRestApiJerseyImpl(apsClient);
this.api = new ApsProtectedRestApiJerseyImpl(apsClient);
}
return this.api;
}
/**
* This method makes the appropriate service calls to find the first APS
* tenant ID from the configured APS service.
*
* This method does not cache the result.
*
* @return An APS tenant ID; null only if there are no tenants.
* @throws MojoExecutionException The APS API failed to initialize.
*/
protected Long findTenantId() throws MojoExecutionException {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
if (tenants == null || tenants.isEmpty())
return null;
return tenants.iterator().next().getId();
}
}

View File

@@ -1,57 +0,0 @@
package com.inteligr8.maven.aps.modeling.goal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.Datum;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.Tenant;
public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal {
@Parameter( property = "aps-model.appName", required = true )
protected String apsAppName;
protected Long findTenantId() {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
if (tenants == null || tenants.isEmpty())
return null;
return tenants.iterator().next().getId();
}
protected Map<String, Long> findAppNameIds() {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
Map<String, Long> apps = new HashMap<>(16);
this.getLog().debug("Searching for all APS Apps");
ResultListDataRepresentation results = api.getModelsApi().get("everyone", null, ModelType.App.getId(), null);
this.getLog().debug("Found " + results.getTotal() + " APS Apps");
for (Datum datum : results.getData()) {
String name = (String)datum.getAdditionalProperties().get("name");
Number id = (Number)datum.getAdditionalProperties().get("id");
apps.put(name, id.longValue());
}
return apps;
}
protected Long findAppId(boolean failOnNotFound) throws MojoExecutionException {
return this.findAppIdByName(this.apsAppName, failOnNotFound);
}
protected Long findAppIdByName(String appName, boolean failOnNotFound) throws MojoExecutionException {
Map<String, Long> apps = this.findAppNameIds();
Long appId = apps.get(this.apsAppName);
if (failOnNotFound && appId == null)
throw new MojoExecutionException("The APS App '" + this.apsAppName + "' could not be found; valid apps: " + apps.keySet());
return appId;
}
}

View File

@@ -0,0 +1,100 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.util.HashMap;
import java.util.Map;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
/**
* This class adds the APS App name to APS addressibility to extending goals.
*
* Only use this class if your goal needs both the APS name and an APS service
* client. You can use `ApsAppGoal` or `ApsAddressibleGoal` if you only need
* one of those capabilities.
*
* @author brian@inteligr8.com
*/
public abstract class ApsAppAddressibleGoal extends ApsAddressibleGoal {
@Parameter( property = "aps-model.appName", required = true )
protected String apsAppName;
/**
* This method makes the appropriate service calls to find all the APS
* Apps, returning them as a map of names to models.
*
* This method does not cache the result.
*
* @return A map of APS App names to their model; may be empty; never null.
* @throws MojoExecutionException The APS API failed to initialize.
*/
protected Map<String, ModelRepresentation> buildAppNameMap() throws MojoExecutionException {
ApsPublicRestApi api = this.getApsApi();
Map<String, ModelRepresentation> apps = new HashMap<>(16);
this.getLog().debug("Searching for all APS Apps");
ResultList<ModelRepresentation> results = api.getModelsApi().get("everyone", null, ModelType.App.getId(), null);
this.getLog().debug("Found " + results.getTotal() + " APS Apps");
for (ModelRepresentation model : results.getData()) {
String name = model.getName();
apps.put(name, model);
}
return apps;
}
/**
* This method makes the appropriate service calls to find the APS App by
* the configured APS App name.
*
* This method does not cache the result.
*
* @param failOnNotFound true to fail if not found; false to return null.
* @return An APS App model; null if not found.
* @throws MojoExecutionException The APS App could not be found.
*/
protected ModelRepresentation findAppModel(boolean failOnNotFound) throws MojoExecutionException {
return this.findAppModelByName(this.apsAppName, failOnNotFound);
}
/**
* This method makes the appropriate service calls to find an APS App by
* the specified APS App name.
*
* This method does not cache the result.
*
* @param apsName An APS App name.
* @param failOnNotFound true to fail if not found; false to return null.
* @return An APS App model; null if not found.
* @throws MojoExecutionException The APS App could not be found.
*/
protected ModelRepresentation findAppModelByName(String appName, boolean failOnNotFound) throws MojoExecutionException {
Map<String, ModelRepresentation> apps = this.buildAppNameMap();
ModelRepresentation appModel = apps.get(this.apsAppName);
if (failOnNotFound && appModel == null)
throw new MojoExecutionException("The APS App '" + this.apsAppName + "' could not be found; valid apps: " + apps.keySet());
return appModel;
}
}

View File

@@ -1,7 +1,26 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import org.apache.maven.plugins.annotations.Parameter;
/**
* This class adds the APS App name to extending goals.
*
* @author brian@inteligr8.com
*/
public abstract class ApsAppGoal extends DisablableGoal {
@Parameter( property = "aps-model.appName", required = true )

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import org.apache.maven.plugin.MojoExecutionException;
@@ -7,6 +21,15 @@ import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.model.AppVersion;
/**
* A class that implements an APS service information goal.
*
* This goal will simply output the APS edition and version to the Maven output
* at the `INFO` level. It is a great way to test your connection
* configuration and network accessibility of the service.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "aps-info", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class ApsInfoGoal extends ApsAddressibleGoal {

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.util.Arrays;
@@ -12,18 +26,28 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.api.ModelsApi;
import com.inteligr8.alfresco.activiti.model.Datum;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.PermissionLevel;
import com.inteligr8.alfresco.activiti.model.PermissionLight;
import com.inteligr8.alfresco.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.ShareInfoRequest;
import com.inteligr8.alfresco.activiti.model.SharePermission;
import com.inteligr8.alfresco.activiti.model.Tenant;
import com.inteligr8.maven.aps.modeling.util.Index;
/**
* A class that implements a way to 'share' APS models.
*
* All APS models are automatically owned by the person that creates them. The
* model is then not viewable or editable by anyone else, except for APS
* administrators. This class provides a way to automatically 'share' the
* synchronized models to groups or individual users. The 'share' may have
* readonly or editing capabilities, as configured.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "share-models", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class ApsShareGoal extends ApsAddressibleGoal {
@@ -82,15 +106,13 @@ public class ApsShareGoal extends ApsAddressibleGoal {
this.shareModels(ModelsApi.ModelType.Form, this.formReaderSet, this.formEditorSet);
}
private void shareModels(ModelsApi.ModelType modelType, Set<String> readers, Set<String> editors) {
ResultListDataRepresentation models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
private void shareModels(ModelsApi.ModelType modelType, Set<String> readers, Set<String> editors) throws MojoExecutionException {
ResultList<ModelRepresentation> models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
if (models.getData() == null)
return;
for (Datum datum : models.getData()) {
Number modelId = (Number)datum.getAdditionalProperties().get("id");
String modelName = (String)datum.getAdditionalProperties().get("name");
if (this.modelName != null && !this.modelName.equals(modelName))
for (ModelRepresentation model : models.getData()) {
if (this.modelName != null && !this.modelName.equals(model.getName()))
continue;
Set<String> groupsAddressed = new HashSet<>();
@@ -98,7 +120,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
Set<String> editorsUnaddressed = new HashSet<>(editors);
ShareInfoRequest changeRequest = new ShareInfoRequest();
ResultList<SharePermission> shares = this.getApsApi().getShareApi().getShareInfo(modelId.toString());
ResultList<SharePermission> shares = this.getApsApi().getShareApi().getShareInfo(model.getId().toString());
if (shares.getData() != null) {
for (SharePermission share : shares.getData()) {
if (share.getGroup() != null) {
@@ -120,7 +142,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
if (!changeRequest.getAdded().isEmpty() || !changeRequest.getUpdated().isEmpty() || !changeRequest.getRemoved().isEmpty()) {
this.getLog().info("Sharing model: " + modelType + " => '" + modelName + "'");
this.getApsApi().getShareApi().setShareInfo(modelId.toString(), changeRequest);
this.getApsApi().getShareApi().setShareInfo(model.getId().toString(), changeRequest);
}
}
}
@@ -196,7 +218,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
return params;
}
protected void buildIdentityIndex() {
protected void buildIdentityIndex() throws MojoExecutionException {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
for (Tenant tenant : tenants) {
List<GroupLight> groups = this.getApsApi().getAdminApi().getGroups(tenant.getId(), true, true);

View File

@@ -0,0 +1,163 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.model.BaseTemplateLight;
import com.inteligr8.alfresco.activiti.model.DocumentTemplateLight;
import com.inteligr8.alfresco.activiti.model.EmailTemplateLight;
/**
* This class adds the APS App name to APS addressibility to extending goals.
*
* Only use this class if your goal needs both the APS name and an APS service
* client. You can use `ApsAppGoal` or `ApsAddressibleGoal` if you only need
* one of those capabilities.
*
* @author brian@inteligr8.com
*/
public abstract class ApsTemplateAddressibleGoal extends ApsAddressibleGoal {
public enum TemplateType {
SystemEmail,
CustomEmail,
Document
}
@Parameter( property = "aps-model.documentTemplateNames", defaultValue = ".*" )
protected String apsDocumentTemplateNames;
@Parameter( property = "aps-model.systemEmailTemplateNames", defaultValue = ".*" )
protected String apsSystemEmailTemplateNames;
@Parameter( property = "aps-model.customEmailTemplateNames", defaultValue = ".*" )
protected String apsCustomEmailTemplateNames;
protected Map<TemplateType, Map<String, ? extends BaseTemplateLight>> findTemplates() throws MojoExecutionException {
Long tenantId = this.findTenantId();
return this.findTemplates(tenantId);
}
protected Map<TemplateType, Map<String, ? extends BaseTemplateLight>> findTemplates(Long tenantId) throws MojoExecutionException {
Map<TemplateType, Map<String, ? extends BaseTemplateLight>> templates = new HashMap<>(32);
Map<String, ? extends BaseTemplateLight> systemEmailTemplateNames = this.findSystemEmailTemplates(tenantId);
if (systemEmailTemplateNames != null && !systemEmailTemplateNames.isEmpty())
templates.put(TemplateType.SystemEmail, systemEmailTemplateNames);
Map<String, ? extends BaseTemplateLight> customEmailTemplateNames = this.findCustomEmailTemplates(tenantId);
if (customEmailTemplateNames != null && !customEmailTemplateNames.isEmpty())
templates.put(TemplateType.CustomEmail, customEmailTemplateNames);
Map<String, ? extends BaseTemplateLight> docTemplateNames = this.findDocumentTemplates(tenantId);
if (docTemplateNames != null && !docTemplateNames.isEmpty())
templates.put(TemplateType.Document, docTemplateNames);
return templates;
}
protected Map<String, ? extends BaseTemplateLight> findSystemEmailTemplates(Long tenantId) throws MojoExecutionException {
this.getLog().debug("Searching for all APS System Email Templates");
List<Pattern> matchingPatterns = this.buildPatterns(this.apsSystemEmailTemplateNames);
Map<String, EmailTemplateLight> map = new HashMap<>();
ResultList<EmailTemplateLight> templates = this.getApsApi().getTemplatesApi().getSystemEmailTemplates(tenantId);
for (EmailTemplateLight template : templates.getData()) {
for (Pattern pattern : matchingPatterns) {
if (pattern.matcher(template.getName()).matches())
map.put(template.getName(), template);
}
}
this.getLog().debug("Found APS System Email Templates: " + map.size());
return map;
}
protected Map<String, ? extends BaseTemplateLight> findCustomEmailTemplates(Long tenantId) throws MojoExecutionException {
this.getLog().debug("Searching for all APS Custom Email Templates");
List<Pattern> matchingPatterns = this.buildPatterns(this.apsCustomEmailTemplateNames);
Map<String, EmailTemplateLight> map = new HashMap<>();
int pageSize = 50;
int page = 1;
ResultList<EmailTemplateLight> templates = this.getApsApi().getTemplatesApi().getCustomEmailTemplates(null, (page-1) * pageSize, pageSize, "sort_by_name_asc", tenantId);
while (!templates.getData().isEmpty()) {
for (EmailTemplateLight template : templates.getData()) {
for (Pattern pattern : matchingPatterns) {
if (pattern.matcher(template.getName()).matches())
map.put(template.getName(), template);
}
}
page++;
templates = this.getApsApi().getTemplatesApi().getCustomEmailTemplates(null, (page-1) * pageSize, pageSize, "sort_by_name_asc", tenantId);
}
this.getLog().debug("Found APS Custom Email Templates: " + map.size());
return map;
}
protected Map<String, ? extends BaseTemplateLight> findDocumentTemplates(Long tenantId) throws MojoExecutionException {
List<Pattern> matchingPatterns = this.buildPatterns(this.apsDocumentTemplateNames);
Map<String, DocumentTemplateLight> map = new HashMap<>();
int pageSize = 50;
int page = 1;
ResultList<DocumentTemplateLight> templates = this.getApsApi().getTemplatesApi().getDocumentTemplates(null, (page-1) * pageSize, pageSize, "sort_by_name_asc", tenantId);
while (!templates.getData().isEmpty()) {
for (DocumentTemplateLight template : templates.getData()) {
for (Pattern pattern : matchingPatterns) {
if (pattern.matcher(template.getName()).matches())
map.put(template.getName(), template);
}
}
page++;
templates = this.getApsApi().getTemplatesApi().getDocumentTemplates(null, (page-1) * pageSize, pageSize, "sort_by_name_asc", tenantId);
}
this.getLog().debug("Found APS Document Templates: " + map.size());
return map;
}
private List<Pattern> buildPatterns(String regexes) {
if (regexes == null)
return Collections.emptyList();
List<Pattern> patterns = new LinkedList<>();
for (String regex : regexes.split(",")) {
regex = regex.trim();
if (regex.length() > 0)
patterns.add(Pattern.compile(regex));
}
return patterns;
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import org.apache.maven.plugin.AbstractMojo;
@@ -5,6 +19,10 @@ import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Parameter;
/**
* This class adds enable/disable capability to any goal. It uses the standard
* 'skip' terminology.
*/
public abstract class DisablableGoal extends AbstractMojo {
@Parameter( property = "aps-model.skip", required = true, defaultValue = "false" )
@@ -24,6 +42,15 @@ public abstract class DisablableGoal extends AbstractMojo {
this.executeEnabled();
}
/**
* Identical to the `AbstractMojo.execute()` method, but it is only called
* when this is enabled.
*
* @see AbstractMojo#execute()
*
* @throws MojoExecutionException The goal failed to execute.
* @throws MojoFailureException The goal failed whiling executing.
*/
public abstract void executeEnabled() throws MojoExecutionException, MojoFailureException;
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.File;
@@ -10,11 +24,20 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
/**
* A class that implements an APS service download goal.
*
* This goal will simply download the APS App with the specified name from the
* specified APS service. The downloaded APS App will remain packed (zipped).
* If a file exists with a conflicting name, it will be overwritten.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "download-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class DownloadAppGoal extends ApsAppAccessibleGoal {
public class DownloadAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.download.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File zipDirectory;
@@ -23,7 +46,7 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateTargetDirectory();
Long appId = this.findAppId(true);
Long appId = this.findAppModel(true).getId();
File appZip = this.downloadApp(appId);
File toAppZip = new File(this.zipDirectory, this.apsAppName + ".zip");
@@ -33,7 +56,7 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
try {
FileUtils.copyFile(appZip, toAppZip);
} catch (IOException ie) {
throw new MojoExecutionException("The downloaded APS App could not be saved", ie);
throw new MojoFailureException("The downloaded APS App could not be saved", ie);
}
}
@@ -42,12 +65,12 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
this.getLog().debug("Creating APS Apps directory: " + this.zipDirectory);
this.zipDirectory.mkdirs();
} else if (!this.zipDirectory.isDirectory()) {
throw new IllegalStateException("The 'targetDirectory' refers to a file and not a directory");
throw new IllegalStateException("The 'zipDirectory' refers to a file and not a directory");
}
}
private File downloadApp(long appId) {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
private File downloadApp(long appId) throws MojoExecutionException {
ApsPublicRestApi api = this.getApsApi();
this.getLog().debug("Downloading APS App: " + appId);
return api.getAppDefinitionsApi().export(appId);
}

View File

@@ -0,0 +1,134 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.alfresco.activiti.model.BaseTemplateLight;
import com.inteligr8.alfresco.activiti.model.EmailTemplate;
import com.inteligr8.maven.aps.modeling.normalizer.ApsTemplateJsonNormalizer;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
import jakarta.ws.rs.core.Response;
/**
* A class that implements an APS service download goal.
*
* This goal will simply download the APS templates with the specified names
* from the specified APS service.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "download-template", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class DownloadTemplateGoal extends ApsTemplateAddressibleGoal {
@Parameter( property = "aps-model.download.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File templateDirectory;
@Parameter( property = "aps-model.normalize" )
protected boolean normalize;
@Parameter( property = "aps-model.normalize.diffFriendly" )
protected boolean diffFriendly;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateTargetDirectory();
Long tenantId = this.findTenantId();
try {
Map<TemplateType, Map<String, ? extends BaseTemplateLight>> templates = this.findTemplates(tenantId);
for (TemplateType ttype : templates.keySet()) {
this.getLog().info("Downloading " + templates.get(ttype).size() + " " + ttype + " templates");
for (Entry<String, ? extends BaseTemplateLight> template : templates.get(ttype).entrySet()) {
switch (ttype) {
case Document:
File dfilebin = new File(this.templateDirectory, template.getValue().getName());
Response response = this.getApsApi().getTemplatesApi().getDocumentTemplate(
template.getValue().getId(),
System.currentTimeMillis());
try {
if (response.getStatus() / 100 == 2) {
InputStream istream = (InputStream) response.getEntity();
try {
FileUtils.copyInputStreamToFile(istream, dfilebin);
} finally {
istream.close();
}
} else {
this.getLog().warn("The document template could not be downloaded; skipping: " + template.getValue().getName());
continue;
}
} finally {
response.close();
}
ObjectNode djson = ModelUtil.getInstance().readPojo(template.getValue());
File dfile = new File(this.templateDirectory, template.getValue().getName() + ".dt.json");
ModelUtil.getInstance().writeJson(djson, dfile, this.diffFriendly);
if (this.normalize)
new ApsTemplateJsonNormalizer(this.diffFriendly).normalizeFile(dfile, null);
break;
case CustomEmail:
EmailTemplate etemplate = this.getApsApi().getTemplatesApi().getCustomEmailTemplate(template.getValue().getId(), tenantId);
ObjectNode ejson = ModelUtil.getInstance().readPojo(etemplate);
File efile = new File(this.templateDirectory, template.getValue().getName() + ".cet.json");
ModelUtil.getInstance().writeJson(ejson, efile, this.diffFriendly);
if (this.normalize)
new ApsTemplateJsonNormalizer(this.diffFriendly).normalizeFile(efile, null);
break;
case SystemEmail:
EmailTemplate stemplate = this.getApsApi().getTemplatesApi().getSystemEmailTemplate(template.getValue().getName(), tenantId);
ObjectNode sjson = ModelUtil.getInstance().readPojo(stemplate);
File sfile = new File(this.templateDirectory, template.getValue().getName() + ".set.json");
ModelUtil.getInstance().writeJson(sjson, sfile, this.diffFriendly);
if (this.normalize)
new ApsTemplateJsonNormalizer(this.diffFriendly).normalizeFile(sfile, null);
}
}
}
} catch (IOException ie) {
throw new MojoFailureException("The downloaded APS templates could not be saved", ie);
}
}
protected void validateTargetDirectory() {
if (!this.templateDirectory.exists()) {
this.getLog().debug("Creating APS template directory: " + this.templateDirectory);
this.templateDirectory.mkdirs();
} else if (!this.templateDirectory.isDirectory()) {
throw new IllegalStateException("The 'templateDirectory' refers to a file and not a directory");
}
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.BufferedInputStream;
@@ -18,6 +32,15 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
/**
* A class that implements an APS App packaging goal.
*
* This goal will simply pack (zip) a folder containing an APS App file
* structure. If a file exists with a conflicting name, it will be
* overwritten.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "pack-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class PackAppGoal extends ApsAppGoal {
@@ -40,7 +63,7 @@ public class PackAppGoal extends ApsAppGoal {
try {
this.pack(appDirectory, targetFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be packed", ie);
throw new MojoFailureException("The APS App could not be packed", ie);
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.IOException;
@@ -8,33 +22,55 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.AppDefinitionPublishRepresentation;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
/**
* A class that implements an APS Service publish goal.
*
* This goal will simply attempt to publish an existing APS App using the APS
* Service. It is typically used in conjuection with the `upload-app` goal.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "publish-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class PublishAppGoal extends ApsAppAccessibleGoal {
public class PublishAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.publish.comment", required = true, defaultValue = "Automated by 'aps-model-maven-plugin'" )
protected String comment;
@Parameter( property = "aps-model.dryRun", required = true, defaultValue = "false" )
protected boolean dryRun;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
Long appId = this.findAppId(false);
Long appId = this.findAppModel(false).getId();
try {
this.publishApp(appId);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be published", ie);
throw new MojoFailureException("The APS App could not be published", ie);
}
}
private void publishApp(Long appId) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
ApsPublicRestApi api = this.getApsApi();
AppDefinitionPublishRepresentation appDefPublish = new AppDefinitionPublishRepresentation();
appDefPublish.setComment(this.comment);
api.getAppDefinitionsApi().publish(appId, appDefPublish);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Publishing app: " + appId);
} else {
this.getLog().info("Publishing app: " + appId);
AppDefinitionUpdateResultRepresentation response = api.getAppDefinitionsApi().publish(appId, appDefPublish);
if (Boolean.TRUE.equals(response.getError()))
throw new WebApplicationException(response.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.File;
@@ -13,9 +27,23 @@ import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawler;
import com.inteligr8.maven.aps.modeling.translator.ApsAppTranslator;
/**
* A class that implements an APS App translation goal.
*
* This goal will translate all the JSON and XML files in an APS App to match
* the environment referenced by the specified APS App. This relies on all APS
* model elements (apps, processes, and forms) to have unique names. The names
* of those model elements are used to remap IDs between environments.
*
* APS does not enforce a unique name constraint. But it is good practice to
* avoid using the same name anyhow. This plugin will just make you do it. It
* will error if a duplicate is detected.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "translate-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class TranslateAppGoal extends ApsAppAccessibleGoal {
public class TranslateAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.app.directory", required = true, defaultValue = "${project.build.directory}/aps/app" )
protected File unzipDirectory;
@@ -46,7 +74,7 @@ public class TranslateAppGoal extends ApsAppAccessibleGoal {
ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, apsAppDirectory, true);
crawler.execute(translator);
} catch (IOException ie) {
throw new MojoExecutionException("An I/O issue occurred", ie);
throw new MojoFailureException("An I/O issue occurred", ie);
} catch (IllegalArgumentException iae) {
throw new MojoExecutionException("The input is not supported", iae);
} catch (IllegalStateException ise) {

View File

@@ -0,0 +1,95 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.maven.aps.modeling.crawler.ApsTemplateCrawler;
import com.inteligr8.maven.aps.modeling.translator.ApsTemplateTranslator;
/**
* A class that implements an APS template translation goal.
*
* This goal will translate all the JSON template files to match the
* environment referenced by the specified APS service. This relies on all APS
* templates having unique names. The names of those templates are used to
* remap IDs between environments.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "translate-template", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class TranslateTemplateGoal extends ApsAddressibleGoal {
@Parameter( property = "aps-model.template.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File templateDirectory;
@Parameter( property = "aps-model.translatedTemplate.directory", required = false, defaultValue = "${project.build.directory}/aps" )
protected File finalDirectory;
@Parameter( property = "aps-model.translate.overwrite", required = true, defaultValue = "true" )
protected boolean overwrite = true;
@Parameter( property = "aps-model.translate.charset", required = true, defaultValue = "utf-8" )
protected String charsetName = "utf-8";
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateApsTemplateDirectory();
this.validateTargetDirectory();
try {
if (!this.templateDirectory.equals(this.finalDirectory)) {
FileUtils.copyDirectory(this.templateDirectory, this.finalDirectory);
}
ApsTemplateTranslator translator = new ApsTemplateTranslator(this.getApsApi());
translator.buildIndexes();
ApsTemplateCrawler crawler = new ApsTemplateCrawler(this.finalDirectory, true);
crawler.execute(translator);
} catch (IOException ie) {
throw new MojoFailureException("An I/O issue occurred", ie);
} catch (IllegalArgumentException iae) {
throw new MojoExecutionException("The input is not supported", iae);
} catch (IllegalStateException ise) {
throw new MojoExecutionException("The state of system is not supported", ise);
}
}
protected void validateApsTemplateDirectory() throws MojoExecutionException {
if (!this.templateDirectory.exists())
throw new MojoExecutionException("The 'templateDirectory' does not exist: " + this.templateDirectory);
if (!this.templateDirectory.isDirectory())
throw new MojoExecutionException("The 'templateDirectory' is not a directory: " + this.templateDirectory);
}
protected void validateTargetDirectory() throws MojoExecutionException {
if (!this.finalDirectory.exists()) {
this.finalDirectory.mkdirs();
} else if (!this.finalDirectory.isDirectory()) {
throw new MojoExecutionException("The 'finalDirectory' is not a directory: " + this.finalDirectory);
}
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.BufferedInputStream;
@@ -29,6 +43,15 @@ import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawler;
import com.inteligr8.maven.aps.modeling.normalizer.ApsAppNormalizer;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* A class that implements an APS App unpackaging goal.
*
* This goal will simply unpack (unzip) an APS App export into a folder. If a
* folder of the APS App name already exists, it will be cleared before the
* export is unpacked into it.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "unpack-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class UnpackAppGoal extends ApsAppGoal {
@@ -67,7 +90,7 @@ public class UnpackAppGoal extends ApsAppGoal {
try {
this.unpack(sourceFile, appDirectory);
} catch (IOException ie) {
throw new MojoExecutionException("The downloaded APS App could not be unpacked", ie);
throw new MojoFailureException("The downloaded APS App could not be unpacked", ie);
}
if (this.reformat)
@@ -78,7 +101,7 @@ public class UnpackAppGoal extends ApsAppGoal {
ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, appDirectory, true);
crawler.execute(normalizer);
} catch (IOException ie) {
throw new MojoExecutionException("An I/O issue occurred", ie);
throw new MojoFailureException("An I/O issue occurred", ie);
}
}
}
@@ -200,7 +223,7 @@ public class UnpackAppGoal extends ApsAppGoal {
file.delete();
}
} catch (TransformerException | SAXException | IOException e) {
throw new MojoFailureException("The following file faild to be reformatted: " + file, e);
throw new MojoFailureException("The following file failed to be reformatted: " + file, e);
}
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.BufferedInputStream;
@@ -12,19 +26,36 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestJerseyApi;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
/**
* A class that implements an APS service upload goal.
*
* This goal will simply upload an APS App package with the specified name to
* the specified APS service. Any IDs specified in the uploaded package must
* match existing IDs for them to properly version. That is the main purpose
* of this plugin and can be achieved using the 'translate-app' goal.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "upload-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class UploadAppGoal extends ApsAppAccessibleGoal {
public class UploadAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.upload.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File zipDirectory;
@Parameter( property = "publish", required = true, defaultValue = "false" )
protected boolean publish = false;
@Parameter( property = "aps-model.dryRun", required = true, defaultValue = "false" )
protected boolean dryRun;
protected final int bufferSize = 128 * 1024;
@@ -32,12 +63,12 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
File sourceFile = this.validateSourceDirectory();
Long appId = this.findAppId(false);
ModelRepresentation appModel = this.findAppModel(false);
try {
this.uploadApp(appId, sourceFile);
this.uploadApp(appModel, sourceFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be uploaded", ie);
throw new MojoFailureException("The APS App could not be uploaded", ie);
}
}
@@ -59,33 +90,49 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
return sourceFile;
}
private void uploadApp(Long appId, File appZip) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
private void uploadApp(ModelRepresentation appModel, File appZip) throws IOException, MojoExecutionException, MojoFailureException {
ApsPublicRestJerseyApi api = this.getApsApi();
FileInputStream fistream = new FileInputStream(appZip);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
FileMultipartJersey multipart = FileMultipartJersey.from(appZip.getName(), bistream);
if (appId == null) {
if (appModel == null) {
if (this.publish) {
this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName);
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new MojoExecutionException(appDefUpdate.getErrorDescription());
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading & publishing new APS App: " + this.apsAppName);
} else {
this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName);
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new WebApplicationException(appDefUpdate.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
} else {
this.getLog().info("Uploading new APS App: " + this.apsAppName);
api.getAppDefinitionsJerseyApi().importApp(multipart, true);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading new APS App: " + this.apsAppName);
} else {
this.getLog().info("Uploading new APS App: " + this.apsAppName);
api.getAppDefinitionsJerseyApi().importApp(multipart, true);
}
}
} else {
if (this.publish) {
this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appId + ")");
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appId, multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new MojoExecutionException(appDefUpdate.getErrorDescription());
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
} else {
this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appModel.getId(), multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new WebApplicationException(appDefUpdate.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
} else {
this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appId + ")");
api.getAppDefinitionsJerseyApi().importApp(appId, multipart, true);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading & versioning APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
} else {
this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
api.getAppDefinitionsJerseyApi().importApp(appModel.getId(), multipart, true);
}
}
}
} catch (ParseException pe) {

View File

@@ -0,0 +1,172 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.inteligr8.alfresco.activiti.model.DocumentTemplateLight;
import com.inteligr8.alfresco.activiti.model.EmailTemplate;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* A class that implements an APS service upload goal.
*
* This goal will simply upload the APS templates within the specified template
* directory to the specified APS service. Any IDs specified in the uploaded
* templates must match existing IDs for them to properly version. That is the
* main purpose of this plugin and can be achieved using the
* 'translate-template' goal.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "upload-template", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class UploadTemplateGoal extends ApsAddressibleGoal {
@Parameter( property = "aps-model.upload.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File templateDirectory;
@Parameter( required = true, defaultValue = "false" )
protected boolean alwaysOverwrite;
@Parameter( property = "aps-model.dryRun", required = true, defaultValue = "false" )
protected boolean dryRun;
protected final int bufferSize = 128 * 1024;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateSourceDirectory();
Long tenantId = this.findTenantId();
for (File file : this.templateDirectory.listFiles()) {
if (!file.getName().endsWith(".json")) {
this.getLog().debug("Ignoring file: " + file.getName());
continue;
}
try {
if (file.getName().endsWith(".dt.json")) {
DocumentTemplateLight template = ModelUtil.getInstance().writePojo(ModelUtil.getInstance().readJsonAsMap(file), DocumentTemplateLight.class);
if (!this.alwaysOverwrite && template.getId() != null && template.getCreated() != null) {
DocumentTemplateLight serverSideTemplate = this.getApsApi().getTemplatesApi().getDocumentTemplate(template.getId());
if (serverSideTemplate != null && !serverSideTemplate.getCreated().isBefore(template.getCreated())) {
this.getLog().debug("Document template unchanged; not updating: " + template.getId());
continue;
}
}
File dfile = new File(file.getParent(), file.getName().substring(0, file.getName().length() - ".dt.json".length()));
if (!dfile.exists())
throw new FileNotFoundException("The file, '" + dfile.getName() + "' was expected and not found");
FileInputStream fistream = new FileInputStream(dfile);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
FileMultipartJersey multipart = FileMultipartJersey.from(dfile.getName(), bistream);
if (template.getId() == null) {
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Creating document template: " + template.getName());
} else {
this.getLog().info("Creating document template: " + template.getName());
this.getApsApi().getTemplatesJerseyApi().createDocumentTemplate(tenantId, multipart);
}
} else {
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Updating document template: " + template.getName());
} else {
this.getLog().info("Updating document template: " + template.getName());
this.getApsApi().getTemplatesJerseyApi().updateDocumentTemplate(template.getId(), tenantId, multipart);
}
}
} finally {
bistream.close();
fistream.close();
}
} else if (file.getName().endsWith(".cet.json")) {
EmailTemplate template = ModelUtil.getInstance().writePojo(ModelUtil.getInstance().readJsonAsMap(file), EmailTemplate.class);
if (!this.alwaysOverwrite && template.getId() != null && template.getCreated() != null) {
EmailTemplate serverSideTemplate = this.getApsApi().getTemplatesApi().getCustomEmailTemplate(template.getId(), tenantId);
if (serverSideTemplate != null && !serverSideTemplate.getCreated().isBefore(template.getCreated())) {
this.getLog().debug("Custom email template unchanged; not updating: " + template.getId());
continue;
}
}
if (template.getId() == null) {
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Creating custom email template: " + template.getName());
} else {
this.getLog().info("Creating custom email template: " + template.getName());
this.getApsApi().getTemplatesJerseyApi().createCustomEmailTemplate(template);
}
} else {
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Updating custom email template: " + template.getName());
} else {
this.getLog().info("Updating custom email template: " + template.getName());
this.getApsApi().getTemplatesJerseyApi().updateCustomEmailTemplate(template.getId(), template);
}
}
} else if (file.getName().endsWith(".set.json")) {
EmailTemplate template = ModelUtil.getInstance().writePojo(ModelUtil.getInstance().readJsonAsMap(file), EmailTemplate.class);
if (!this.alwaysOverwrite) {
EmailTemplate serverSideTemplate = this.getApsApi().getTemplatesApi().getSystemEmailTemplate(template.getName(), tenantId);
if (serverSideTemplate != null && template.getTemplate().equals(serverSideTemplate.getTemplate())) {
this.getLog().debug("System email template unchanged; not updating: " + template.getName());
continue;
}
}
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Updating system email template: " + template.getName());
} else {
this.getLog().info("Updating system email template: " + template.getName());
this.getApsApi().getTemplatesJerseyApi().updateSystemEmailTemplate(template.getName(), template);
}
}
} catch (JsonProcessingException jpe) {
throw new MojoFailureException("The APS templates JSON files could not be parsed", jpe);
} catch (IOException ie) {
throw new MojoFailureException("The APS templates could not be uploaded", ie);
} catch (ParseException pe) {
throw new MojoFailureException("This should never happen", pe);
}
}
}
protected void validateSourceDirectory() {
if (!this.templateDirectory.exists()) {
throw new IllegalStateException("The 'templateDirectory' does not exist: " + this.templateDirectory);
} else if (!this.templateDirectory.isDirectory()) {
throw new IllegalStateException("The 'templateDirectory' is not a directory: " + this.templateDirectory);
}
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
@@ -10,21 +24,39 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.ApsModelArrayNodeSorter;
import com.inteligr8.maven.aps.modeling.util.ArrayNodeObjectSorter;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS App JSON configuration file normalizer.
*
* This will remove the 'lastUpdated' date of each defined process model. It
* will also order the process 'models' by their respective 'name' values.
*
* @author brian@inteligr8.com
*/
public class ApsAppJsonNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsAppJsonNormalizer.class);
private final boolean enableSorting;
/**
* This constructor initializes the default normalizer with or without
* sorting.
*
* The sorting feature is available for a better "diff" experience. If
* you intend to commit the APS App configurations to Git, you will want to
* enable sorting.
*
* @param enableSorting true to re-order JSON objects; false to keep as-is.
*/
public ApsAppJsonNormalizer(boolean enableSorting) {
this.enableSorting = enableSorting;
}
@Override
public void transformFile(File file, String modelName, Long modelId) throws IOException {
public void normalizeFile(File file, String modelName) throws IOException {
this.logger.debug("Normalizing App JSON file: {}", file);
boolean changed = false;
@@ -34,20 +66,27 @@ public class ApsAppJsonNormalizer implements ApsFileNormalizer {
// remove system-level and non-functional fields from models
for (JsonNode _jsonModel : jsonModels) {
ObjectNode jsonModel = (ObjectNode)_jsonModel;
int fields = jsonModel.size();
jsonModel.remove(Arrays.asList("lastUpdated"));
// jsonModel.remove(Arrays.asList("createdBy", "createdByFullName", "lastUpdatedBy", "lastUpdatedByFullName", "lastUpdated"));
if (jsonModel.size() < fields)
changed = true;
changed = this.transformModel(jsonModel) || changed;
}
// sort the models for better 'diff' support
if (this.enableSorting)
changed = ApsModelArrayNodeSorter.getInstance().sort(jsonModels, "name") || changed;
if (this.enableSorting) {
boolean sorted = ArrayNodeObjectSorter.getInstance().sort(jsonModels, "name");
this.logger.trace("Sorted App models: {}: {}", modelName, sorted);
changed = sorted || changed;
}
if (changed)
ModelUtil.getInstance().writeJson(descriptor, file, this.enableSorting);
}
private boolean transformModel(ObjectNode jsonModel) {
this.logger.trace("Removing excess App model meta-data: {}", jsonModel.get("name"));
int fields = jsonModel.size();
jsonModel.remove(Arrays.asList("lastUpdated"));
// jsonModel.remove(Arrays.asList("createdBy", "createdByFullName", "lastUpdatedBy", "lastUpdatedByFullName", "lastUpdated"));
return jsonModel.size() < fields;
}
}

View File

@@ -1,12 +1,54 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawlable;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
/**
* This class defines an APS App package normalizer.
*
* An APS App package is a ZIP file that contains multiple files in a
* predictable folder hierachy with predictable file names. It is effectively
* an APS App interchange format specification.
*
* A package must have at least a single configuration file at the root of the
* ZIP package in the JSON format. It must be named the APS App name. That
* file will then reference all the model elements contained in the ZIP. Any
* model elements not referenced will simply be ignored by APS and by this
* plugin.
*
* This class has methods that provide normalizer for all the various model
* elements in an APS App package.
*
* @author brian@inteligr8.com
*/
public class ApsAppNormalizer implements ApsAppCrawlable {
private final boolean enableSorting;
/**
* This constructor initializes the default normalizer with or without
* sorting.
*
* The sorting feature is available for a better "diff" experience. If
* you intend to commit the APS App configurations to Git, you will want to
* enable sorting.
*
* @param enableSorting true to re-order JSON objects; false to keep as-is.
*/
public ApsAppNormalizer(boolean enableSorting) {
this.enableSorting = enableSorting;
}
@@ -20,6 +62,11 @@ public class ApsAppNormalizer implements ApsAppCrawlable {
public ApsFileTransformer getFormJsonTransformer() {
return new ApsFormJsonNormalizer();
}
@Override
public ApsFileTransformer getDecisionTableJsonTransformer() {
return new ApsDecisionTableJsonNormalizer();
}
@Override
public ApsFileTransformer getProcessJsonTransformer() {

View File

@@ -0,0 +1,41 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class implements an APS Decision Table JSON configuration file
* normalizer.
*
* This does nothing but log at this time.
*
* @author brian@inteligr8.com
*/
public class ApsDecisionTableJsonNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsDecisionTableJsonNormalizer.class);
@Override
public void normalizeFile(File file, String modelName) throws IOException {
this.logger.debug("Normalizing Form JSON file: {}", file);
this.logger.trace("Nothing to normalize: {}", modelName);
}
}

View File

@@ -1,7 +1,48 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
import java.io.IOException;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
/**
* This interface defines an APS file normalizer.
*
* Normalization is the transformation of something from one state to a target
* (normalized) state. In this context, a configuration is being normalized to
* be diff-friendly. That principally means the removal of dates. In theory,
* two normalized files will always match unless something of real substance
* has changed.
*/
public interface ApsFileNormalizer extends ApsFileTransformer {
@Override
default void transformFile(File file, String modelName, Long modelId) throws IOException {
this.normalizeFile(file, modelName);
}
/**
* This method normalizes the specified file which should be referred to
* with the specified name.
*
* @param file A model file.
* @param modelName The model name.
* @throws IOException An I/O related exception has occurred during normalization.
*/
void normalizeFile(File file, String modelName) throws IOException;
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
@@ -6,14 +20,21 @@ import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class implements an APS Form JSON configuration file normalizer.
*
* This does nothing but log at this time.
*
* @author brian@inteligr8.com
*/
public class ApsFormJsonNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsFormJsonNormalizer.class);
@Override
public void transformFile(File file, String modelName, Long modelId) throws IOException {
public void normalizeFile(File file, String modelName) throws IOException {
this.logger.debug("Normalizing Form JSON file: {}", file);
this.logger.trace("Nothing to normalize: {}", file);
this.logger.trace("Nothing to normalize: {}", modelName);
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
@@ -14,6 +28,14 @@ import org.xml.sax.SAXException;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS Process BPMN/XML configuration file normalizer.
*
* This will remove the 'exportDateTime' and 'modelLastUpdated' dates and
* 'modelVersion' of the process model.
*
* @author brian@inteligr8.com
*/
public class ApsProcessBpmnNormalizer implements ApsFileNormalizer {
private static final String NAMESPACE_ACTIVITI_MODELER = "http://activiti.com/modeler";
@@ -21,7 +43,7 @@ public class ApsProcessBpmnNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsProcessBpmnNormalizer.class);
@Override
public void transformFile(File file, String modelName, Long modelId) throws IOException {
public void normalizeFile(File file, String modelName) throws IOException {
this.logger.debug("Normalizing Process BPMN file: {}", file);
boolean changed = false;
@@ -32,6 +54,8 @@ public class ApsProcessBpmnNormalizer implements ApsFileNormalizer {
changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "exportDateTime") || changed;
changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelLastUpdated") || changed;
changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion") || changed;
//changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion", "0") || changed;
if (changed)
ModelUtil.getInstance().writeXml(bpmn, file);

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
@@ -9,21 +23,39 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.ApsModelArrayNodeSorter;
import com.inteligr8.maven.aps.modeling.util.ArrayNodeObjectSorter;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS Process JSON configuration file normalizer.
*
* This will do nothing unless sorting is enabled. With sorting, it will order
* the 'childShapes' by their respective 'resourceId' values.
*
* @author brian@inteligr8.com
*/
public class ApsProcessJsonNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsProcessJsonNormalizer.class);
private final boolean enableSorting;
/**
* This constructor initializes the default normalizer with or without
* sorting.
*
* The sorting feature is available for a better "diff" experience. If
* you intend to commit the APS App configurations to Git, you will want to
* enable sorting.
*
* @param enableSorting true to re-order JSON objects; false to keep as-is.
*/
public ApsProcessJsonNormalizer(boolean enableSorting) {
this.enableSorting = enableSorting;
}
@Override
public void transformFile(File file, String modelName, Long modelId) throws IOException {
public void normalizeFile(File file, String modelName) throws IOException {
this.logger.debug("Normalizing Process JSON file: {}", file);
if (!this.enableSorting)
@@ -33,8 +65,10 @@ public class ApsProcessJsonNormalizer implements ApsFileNormalizer {
ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class);
ArrayNode jsonChildShapes = this.getChildShapes(jsonDescriptor);
this.logger.trace("Found Process shapes: {}: {}", modelName, jsonChildShapes.size());
changed = ApsModelArrayNodeSorter.getInstance().sort(jsonChildShapes, "resourceId") || changed;
changed = ArrayNodeObjectSorter.getInstance().sort(jsonChildShapes, "resourceId") || changed;
this.logger.trace("Sorted Process shapes: {}: {}", modelName, changed);
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, this.enableSorting);

View File

@@ -0,0 +1,73 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS template JSON configuration file normalizer.
*
* This will remove the 'createdBy' of each defined template.
*
* @author brian@inteligr8.com
*/
public class ApsTemplateJsonNormalizer implements ApsFileNormalizer {
private final Logger logger = LoggerFactory.getLogger(ApsTemplateJsonNormalizer.class);
private final boolean enableSorting;
/**
* This constructor initializes the default normalizer with or without
* sorting.
*
* The sorting feature is available for a better "diff" experience. If
* you intend to commit the APS App configurations to Git, you will want to
* enable sorting.
*
* @param enableSorting true to re-order JSON objects; false to keep as-is.
*/
public ApsTemplateJsonNormalizer(boolean enableSorting) {
this.enableSorting = enableSorting;
}
@Override
public void normalizeFile(File file, String _unused) throws IOException {
this.logger.debug("Normalizing template JSON file: {}", file);
ObjectNode templateJson = (ObjectNode) ModelUtil.getInstance().readJson(file);
boolean changed = this.transformModel(templateJson);
if (changed)
ModelUtil.getInstance().writeJson(templateJson, file, this.enableSorting);
}
private boolean transformModel(ObjectNode jsonModel) {
this.logger.trace("Removing excess template meta-data: {}", jsonModel.get("name"));
int fields = jsonModel.size();
jsonModel.remove(Arrays.asList("createdBy"));
return jsonModel.size() < fields;
}
}

View File

@@ -0,0 +1,48 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.normalizer;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
import com.inteligr8.maven.aps.modeling.crawler.ApsTemplateCrawlable;
/**
* This class defines an APS template normalizer.
*
* @author brian@inteligr8.com
*/
public class ApsTemplateNormalizer implements ApsTemplateCrawlable {
private final boolean enableSorting;
/**
* This constructor initializes the default normalizer with or without
* sorting.
*
* The sorting feature is available for a better "diff" experience. If
* you intend to commit the APS template configurations to Git, you will
* want to enable sorting.
*
* @param enableSorting true to re-order JSON objects; false to keep as-is.
*/
public ApsTemplateNormalizer(boolean enableSorting) {
this.enableSorting = enableSorting;
}
@Override
public ApsFileTransformer getTemplateJsonTransformer() {
return new ApsTemplateJsonNormalizer(this.enableSorting);
}
}

View File

@@ -1,7 +1,22 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import org.slf4j.Logger;
@@ -14,6 +29,19 @@ import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS App JSON configuration file translator.
*
* This will translate the IDs of all the APS App, Processes, and Organizations
* referenced in the APS App descriptor.
*
* Caution: in lieu of implementing APS User translation, APS User identity
* references are simply removed. Using APS Users in your APS models is bad
* practice. Use APS Organizations, even if that user is the only member of
* the group.
*
* @author brian@inteligr8.com
*/
public class ApsAppJsonTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsAppJsonTranslator.class);
@@ -21,25 +49,45 @@ public class ApsAppJsonTranslator extends ApsOrganizationHandler implements ApsF
private final Index<Long, String> apsProcessIndex;
private final Index<Long, String> fileProcessIndex;
/**
* This constructor initializes the default translator.
*
* @param api An APS API reference.
* @param apsProcessIndex A map of process IDs to process names as defined in the APS Service.
* @param apsOrgIndex A map of organizations (groups) to organization meta-data as defined in the APS Service.
* @param fileProcessIndex A map of process IDs to process names as used in the local process files.
*/
public ApsAppJsonTranslator(
ApsPublicRestApi api,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> fileProcessIndex) {
super(api, apsOrgIndex);
this.apsOrgIndex = apsOrgIndex;
this.apsProcessIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.fileProcessIndex = fileProcessIndex;
}
@Override
public void translateFile(File file, String modelName, Long modelId) throws IOException {
public void translateFile(File file, String appName, Long appId) throws IOException {
this.logger.debug("Translating JSON file: {}", file);
boolean changed = false;
JsonNode descriptor = ModelUtil.getInstance().readJson(file);
JsonNode jsonDescriptor = ModelUtil.getInstance().readJson(file);
for (JsonNode _jsonModel : descriptor.get("definition").get("models")) {
changed = this.translateModels(jsonDescriptor, appName) || changed;
changed = this.translateIdentities(jsonDescriptor, appName) || changed;
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, false);
}
private boolean translateModels(JsonNode jsonDescriptor, String appName) {
this.logger.trace("Translating processes in App: {}", appName);
boolean changed = false;
for (JsonNode _jsonModel : jsonDescriptor.get("definition").get("models")) {
ObjectNode jsonModel = (ObjectNode)_jsonModel;
String fileProcessName = jsonModel.get("name").asText();
@@ -77,45 +125,78 @@ public class ApsAppJsonTranslator extends ApsOrganizationHandler implements ApsF
// changed = true;
}
for (JsonNode _jsonIdentityInfo : descriptor.get("definition").get("publishIdentityInfo")) {
ObjectNode jsonGroup = (ObjectNode)_jsonIdentityInfo.get("group");
return changed;
}
private boolean translateIdentities(JsonNode jsonDescriptor, String appName) {
this.logger.trace("Translating identities in App: {}", appName);
boolean changed = false;
Iterator<JsonNode> i = jsonDescriptor.get("definition").get("publishIdentityInfo").iterator();
while (i.hasNext()) {
JsonNode jsonIdentityInfo = i.next();
String fileOrgName = jsonGroup.get("name").asText();
this.logger.trace("Found organization '{}' in APS App descriptor", fileOrgName);
String identityType = jsonIdentityInfo.get("type").asText();
if (identityType == null) {
this.logger.warn("Found identity with no type; ignoring ...");
continue;
}
if (this.apsOrgIndex.containsKey(fileOrgName)) {
long fileOrgId = jsonGroup.get("id").asLong();
GroupLight apsOrg = this.apsOrgIndex.get(fileOrgName);
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; adding to APS", fileOrgName);
long apsGroupId = this.createOrganization(fileOrgName);
jsonGroup.put("id", apsGroupId);
switch (identityType.toLowerCase()) {
case "user":
this.logger.warn("Found 'user' identity, which should never be used for portablility; removing ...");
changed = true;
} else if (fileOrgId != apsOrg.getId()) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrg.getId());
jsonGroup.put("id", apsOrg.getId());
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName);
}
if (jsonGroup.has("parentGroupId")) {
jsonGroup.put("parentGroupId", 0);
changed = true;
}
// if (jsonGroup.has("parentGroupId")) {
// jsonGroup.remove("parentGroupId");
// changed = true;
// }
} else {
this.logger.warn("The organization '{}' does not exist in APS", fileOrgName);
// FIXME create the organization
i.remove();
break;
case "group":
changed = this.translateGroup(jsonIdentityInfo) || changed;
break;
default:
this.logger.warn("Found '{}' identity, which is not expected; ignoring ...", identityType);
}
}
if (changed)
ModelUtil.getInstance().writeJson(descriptor, file, false);
return changed;
}
private boolean translateGroup(JsonNode jsonIdentityInfo) {
boolean changed = false;
ObjectNode jsonGroup = (ObjectNode)jsonIdentityInfo.get("group");
String fileOrgName = jsonGroup.get("name").asText();
this.logger.trace("Found organization '{}' in APS App descriptor", fileOrgName);
if (this.apsOrgIndex.containsKey(fileOrgName)) {
long fileOrgId = jsonGroup.get("id").asLong();
GroupLight apsOrg = this.apsOrgIndex.get(fileOrgName);
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; adding to APS", fileOrgName);
long apsGroupId = this.createOrganization(fileOrgName);
jsonGroup.put("id", apsGroupId);
changed = true;
} else if (fileOrgId != apsOrg.getId()) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrg.getId());
jsonGroup.put("id", apsOrg.getId());
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName);
}
if (jsonGroup.has("parentGroupId")) {
jsonGroup.put("parentGroupId", 0);
changed = true;
}
// if (jsonGroup.has("parentGroupId")) {
// jsonGroup.remove("parentGroupId");
// changed = true;
// }
} else {
this.logger.warn("The organization '{}' does not exist in APS", fileOrgName);
// FIXME create the organization
}
return changed;
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
@@ -12,17 +26,34 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.Datum;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.Tenant;
import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawlable;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
import com.inteligr8.maven.aps.modeling.util.Index;
/**
* This class defines an APS App package translator.
*
* An APS App package is a ZIP file that contains multiple files in a
* predictable folder hierachy with predictable file names. It is effectively
* an APS App interchange format specification.
*
* A package must have at least a single configuration file at the root of the
* ZIP package in the JSON format. It must be named the APS App name. That
* file will then reference all the model elements contained in the ZIP. Any
* model elements not referenced will simply be ignored by APS and by this
* plugin.
*
* This class has methods that provide translator for all the various model
* elements in an APS App package.
*
* @author brian@inteligr8.com
*/
public class ApsAppTranslator implements ApsAppCrawlable {
private final Logger logger = LoggerFactory.getLogger(ApsAppTranslator.class);
@@ -33,16 +64,33 @@ public class ApsAppTranslator implements ApsAppCrawlable {
private boolean indexesBuilt = false;
private Map<String, GroupLight> apsOrgIndex;
private Index<Long, String> apsFormIndex;
private Index<Long, String> apsDecisionTableIndex;
private Index<Long, String> apsProcessIndex;
private Index<Long, String> fileFormIndex;
private Index<Long, String> fileProcessIndex;
private Index<Long, String> fileSubprocessIndex;
public ApsAppTranslator(File apsAppDirectory, ApsPublicRestApiJerseyImpl api) {
/**
* This constructor initializes the default translator.
*
* The specified directory and a reference to the APS Service are both
* required. This way the configuration file can be cross-referenced with
* the APS App IDs on the target APS instance.
*
* @param apsAppDirectory The directory of the unpacked APS package.
* @param api An APS API reference.
*/
public ApsAppTranslator(File apsAppDirectory, ApsPublicRestApi api) {
this.api = api;
this.appDirectory = apsAppDirectory;
}
/**
* This method initializes the data required from the APS Service for the
* function of this class.
*
* @throws IOException A network I/O related issue occurred.
*/
public synchronized void buildIndexes() throws IOException {
if (this.indexesBuilt)
return;
@@ -60,6 +108,9 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.apsFormIndex = this.buildApsModelIndex(ModelType.Form);
this.logLarge("APS form models: {}", this.apsFormIndex);
this.apsDecisionTableIndex = this.buildApsModelIndex(ModelType.DecisionTable);
this.logLarge("APS decision table models: {}", this.apsDecisionTableIndex);
this.fileFormIndex = this.buildFileIndex("form-models", this.apsFormIndex, true);
this.logLarge("File form models: {}", this.fileFormIndex);
@@ -96,8 +147,8 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return new ApsAppJsonTranslator(
this.api,
this.apsOrgIndex,
this.apsProcessIndex,
this.apsOrgIndex,
this.fileProcessIndex);
}
@@ -109,6 +160,15 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return new ApsFormJsonTranslator(
this.apsFormIndex);
}
@Override
public ApsFileTransformer getDecisionTableJsonTransformer() {
if (!this.indexesBuilt)
throw new IllegalStateException("The indexes are never built");
return new ApsDecisionTableJsonTranslator(
this.apsDecisionTableIndex);
}
@Override
public ApsFileTransformer getProcessJsonTransformer() {
@@ -119,7 +179,8 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex);
this.apsFormIndex,
this.apsDecisionTableIndex);
}
@Override
@@ -132,6 +193,7 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex,
this.apsDecisionTableIndex,
this.fileFormIndex);
}
@@ -149,17 +211,15 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return map;
}
protected Index<Long, String> buildApsModelIndex(ModelType modelType) {
ResultListDataRepresentation results = this.api.getModelsApi().get("everyone", null, modelType.getId(), null);
protected Index<Long, String> buildApsModelIndex(ModelType modelType) {
ResultList<ModelRepresentation> results = this.api.getModelsApi().get("everyone", null, modelType.getId(), null);
this.logger.debug("APS {} models found: {} out of {}", modelType, results.getSize(), results.getTotal());
Index<Long, String> map = new Index<>(results.getSize().intValue(), false);
try {
for (Datum datum : results.getData()) {
Number defId = (Number)datum.getAdditionalProperties().get("id");
String defName = (String)datum.getAdditionalProperties().get("name");
map.put(defId.longValue(), defName);
for (ModelRepresentation model : results.getData()) {
map.put(model.getId(), model.getName());
// FIXME add paging support
}

View File

@@ -0,0 +1,39 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import com.inteligr8.maven.aps.modeling.util.Index;
/**
* This class implements an APS Decision Table JSON configuration file
* translator.
*
* This will translate the decision table ID embedded into APS Decision Table
* descriptor.
*
* @author brian@inteligr8.com
*/
public class ApsDecisionTableJsonTranslator extends ApsResourceJsonTranslator {
/**
* This constructor initializes the default translator.
*
* @param apsDecisionTableIndex A map of form IDs to form names as defined in the APS Service.
*/
public ApsDecisionTableJsonTranslator(Index<Long, String> apsDecisionTableIndex) {
super(apsDecisionTableIndex);
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
@@ -5,6 +19,14 @@ import java.io.IOException;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
/**
* This interface defines an APS file translator.
*
* Translation is the transformation of something from one state to a target
* state. In this context, a configuration is being translated to be import
* friendly for the target APS Service. That principally means the update
* of APS model IDs.
*/
public interface ApsFileTranslator extends ApsFileTransformer {
@Override
@@ -12,6 +34,15 @@ public interface ApsFileTranslator extends ApsFileTransformer {
this.translateFile(file, modelName, modelId);
}
/**
* This method translates the specified file which should be referred to
* with the specified name and ID.
*
* @param file A model file.
* @param modelName The target model name.
* @param modelId The target model ID.
* @throws IOException An I/O related exception has occurred during translation.
*/
void translateFile(File file, String modelName, Long modelId) throws IOException;
}

View File

@@ -1,52 +1,37 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsFormJsonTranslator implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsFormJsonTranslator.class);
private final Index<Long, String> apsIndex;
/**
* This class implements an APS Form JSON configuration file translator.
*
* This will translate the form ID embedded into APS Form descriptor.
*
* @author brian@inteligr8.com
*/
public class ApsFormJsonTranslator extends ApsResourceJsonTranslator {
/**
* This constructor initializes the default translator.
*
* @param apsFormIndex A map of form IDs to form names as defined in the APS Service.
*/
public ApsFormJsonTranslator(Index<Long, String> apsFormIndex) {
this.apsIndex = apsFormIndex;
}
@Override
public void translateFile(File file, String formName, Long formId) throws IOException {
this.logger.debug("Translating JSON file: {}", file);
boolean changed = false;
ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class);
JsonNode jsonResourceId = jsonDescriptor.get("resourceId");
if (jsonResourceId == null || jsonResourceId.isNull()) {
this.logger.debug("The form has no ID to translate");
return;
}
this.logger.trace("Found ID {} in the '{}' APS Form descriptor", jsonResourceId, formName);
Long apsFormId = this.apsIndex.getFirstKey(formName);
if (apsFormId == null) {
this.logger.debug("The form '{}' does not exist in APS; leaving unchanged", formName);
} else if (!formId.equals(apsFormId)) {
this.logger.debug("The form '{}' exists in APS with ID {}; changing descriptor", formName, apsFormId);
jsonDescriptor.put("resourceId", apsFormId);
changed = true;
} else {
this.logger.trace("The form '{}' ID does not change; leaving unchanged", formName, apsFormId);
}
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, false);
super(apsFormIndex);
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.util.Map;
@@ -9,12 +23,21 @@ import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.model.GroupLight;
/**
* This class provides APS Organization support.
*
* @author brian@inteligr8.com.
*/
public class ApsOrganizationHandler {
private final Logger logger = LoggerFactory.getLogger(ApsOrganizationHandler.class);
private final ApsPublicRestApi api;
private final Map<String, GroupLight> apsOrgIndex;
/**
* @param api An APS API reference.
* @param apsOrgIndex A map of organization IDs to organization meta-data defined in the APS service.
*/
public ApsOrganizationHandler(
ApsPublicRestApi api,
Map<String, GroupLight> apsOrgIndex) {

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
@@ -27,6 +41,19 @@ import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS Process BPMN/XML configuration file translator.
*
* This will translate the IDs of all the APS Process, Organizations, and
* Forms referenced in the APS Process BPMN.
*
* Caution: in lieu of implementing APS User translation, APS User identity
* references are simply removed. Using APS Users in your APS models is bad
* practice. Use APS Organizations, even if that user is the only member of
* the group.
*
* @author brian@inteligr8.com
*/
public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private static final String NAMESPACE_ACTIVITI = "http://activiti.org/bpmn";
@@ -36,19 +63,31 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
private final Index<Long, String> apsIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
private final Index<Long, String> fileFormIndex;
private final Index<Long, String> apsDecisionTableIndex;
private final Index<Long, String> fileFormIndex;
/**
* This constructor initializes the default translator.
*
* @param api An APS API reference.
* @param apsProcessIndex A map of process IDs to process names as defined in the APS Service.
* @param apsOrgIndex A map of organizations (groups) to organization meta-data as defined in the APS Service.
* @param apsFormIndex A map of form IDs to form names as defined in the APS Service.
* @param fileFormIndex A map of form IDS to form names as used in the local form files.
*/
public ApsProcessBpmnTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex,
Index<Long, String> apsDecisionTableIndex,
Index<Long, String> fileFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.fileFormIndex = fileFormIndex;
this.apsDecisionTableIndex = apsDecisionTableIndex;
this.fileFormIndex = fileFormIndex;
}
@Override
@@ -75,6 +114,12 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
Element formKeyElement = (Element)formKeyElements.item(n);
changed = this.translateTaskForm(formKeyElement) || changed;
}
NodeList decisionTableRefElements = ModelUtil.getInstance().xpath(definitionsElement, "//modeler:decisiontable-reference");
for (int n = 0; n < decisionTableRefElements.getLength(); n++) {
Element decisionTableRefElement = (Element)decisionTableRefElements.item(n);
changed = this.translateDecisionTableRef(decisionTableRefElement, bpmn) || changed;
}
NodeList subprocessElements = ModelUtil.getInstance().xpath(definitionsElement, "//tns:subProcess");
for (int n = 0; n < subprocessElements.getLength(); n++) {
@@ -106,8 +151,6 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
if (apsProcessId != null)
changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelId", apsProcessId) || changed;
changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion") || changed;
//changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion", "0") || changed;
return changed;
}
@@ -224,6 +267,47 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
return changed;
}
private boolean translateDecisionTableRef(Element decisionTableRefElement, Document xmldoc) throws XPathExpressionException {
String dtRefIdStr = decisionTableRefElement.getAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferenceid");
this.logger.trace("Translating decision table: {}", dtRefIdStr);
if (dtRefIdStr == null)
throw new IllegalStateException("A decision table was detected, but no identifier was found");
Long dtRefId = new Long(dtRefIdStr);
boolean changed = false;
String dtRefName = decisionTableRefElement.getAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferencename");
if (dtRefName == null)
throw new IllegalStateException("A decision table was detected, but no name was found: " + dtRefId);
dtRefName = dtRefName.trim();
this.logger.trace("Found '{}' decision table in the APS Process BPMN model", dtRefName);
Long apsDecisionTableId = this.apsDecisionTableIndex.getFirstKey(dtRefName);
if (apsDecisionTableId == null) {
this.logger.debug("The decision table '{}' does not exist in APS; leaving unchanged", dtRefName);
return false;
}
this.logger.trace("Found ID {} for the decision table '{}' in the APS Process BPMN model", apsDecisionTableId, dtRefName);
String dtRefKey = (String) ModelUtil.getInstance().xpath(
decisionTableRefElement.getParentNode(),
"activiti:field[@name=\"decisionTableReferenceKey\"]/activiti:string/text()",
XPathConstants.STRING);
if (dtRefKey == null)
throw new IllegalStateException("A decision table was detected, but no key was found: " + dtRefId);
if (!apsDecisionTableId.equals(dtRefId)) {
this.logger.debug("The decision table '{}' exists in APS with ID {}; changing model", dtRefName, apsDecisionTableId);
decisionTableRefElement.setAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferenceid", apsDecisionTableId.toString());
changed = true;
} else {
this.logger.trace("The decision table '{}' key does not change; leaving unchanged", dtRefName);
}
return changed;
}
private boolean translateSubprocess(Element subprocessElement) throws XPathExpressionException {
this.logger.trace("Translating subprocess: {}", subprocessElement.getAttribute("id"));
@@ -340,14 +424,5 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
attr.setValue(value.toString());
return true;
}
private boolean removeAttributeIfSet(Element element, String attrNamespace, String attrName) {
Attr attr = element.getAttributeNodeNS(attrNamespace, attrName);
if (attr == null)
return false;
element.removeAttributeNode(attr);
return true;
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
@@ -15,22 +29,47 @@ import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS Process JSON configuration file translator.
*
* This will translate the IDs of all the APS Process, Organizations, and
* Forms referenced in the APS Process descriptor.
*
* Caution: in lieu of implementing APS User translation, APS User identity
* references are simply removed. Using APS Users in your APS models is bad
* practice. Use APS Organizations, even if that user is the only member of
* the group.
*
* @author brian@inteligr8.com
*/
public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsProcessJsonTranslator.class);
private final Index<Long, String> apsIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
private final Index<Long, String> apsDecisionTableIndex;
/**
* This constructor initializes the default translator.
*
* @param api An APS API reference.
* @param apsProcessIndex A map of process IDs to process names as defined in the APS Service.
* @param apsOrgIndex A map of organizations (groups) to organization meta-data as defined in the APS Service.
* @param apsFormIndex A map of form IDs to form names as defined in the APS Service.
* @param apsDecisionTableIndex A map of decision table IDs to decision table names as defined in the APS Service.
*/
public ApsProcessJsonTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex) {
Index<Long, String> apsFormIndex,
Index<Long, String> apsDecisionTableIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.apsDecisionTableIndex = apsDecisionTableIndex;
}
@Override
@@ -42,21 +81,22 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class);
changed = this.translateResourceId(jsonDescriptor, apsProcessId) || changed;
changed = this.translateOrganizations(jsonDescriptor) || changed;
changed = this.translateForms(jsonDescriptor) || changed;
changed = this.translateSubprocesses(jsonDescriptor) || changed;
changed = this.translateResourceId(jsonDescriptor, processName, apsProcessId) || changed;
changed = this.translateOrganizations(jsonDescriptor, processName) || changed;
changed = this.translateResources(jsonDescriptor, processName) || changed;
changed = this.translateSubprocesses(jsonDescriptor, processName) || changed;
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, false);
}
private boolean translateResourceId(ObjectNode jsonDescriptor, Long apsProcessId) {
private boolean translateResourceId(ObjectNode jsonDescriptor, String processName, Long apsProcessId) {
if (apsProcessId == null) {
this.logger.trace("The process is not in APS; treating as new");
return false;
}
this.logger.trace("Translating resource ID in process: {}", processName);
ObjectNode resourceIdParentJson = jsonDescriptor;
JsonNode jsonResourceId = jsonDescriptor.get("resourceId");
@@ -83,7 +123,8 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
private boolean translateOrganizations(ObjectNode jsonDescriptor) {
private boolean translateOrganizations(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating organizations in process: {}", processName);
boolean changed = false;
for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) {
@@ -134,7 +175,8 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
private boolean translateForms(ObjectNode jsonDescriptor) {
private boolean translateResources(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating resources in process: {}", processName);
boolean changed = false;
for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) {
@@ -144,6 +186,9 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
JsonNode jsonFormRef = (JsonNode)jsonChildShape.get("properties").get("formreference");
changed = this.translateReference(jsonFormRef, this.apsFormIndex) || changed;
JsonNode jsonDecitionTableRef = (JsonNode)jsonChildShape.get("properties").get("decisiontaskdecisiontablereference");
changed = this.translateReference(jsonDecitionTableRef, this.apsDecisionTableIndex) || changed;
} catch (NullPointerException npe) {
// suppress; gracefully skip
}
@@ -157,18 +202,24 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
return false;
this.logger.trace("Found a conditional sequence flow in the APS Process descriptor");
ObjectNode expression = (ObjectNode)jsonConditionalSequenceFlow.get("expression");
boolean changed = false;
changed = this.translateNamedId(expression, this.apsFormIndex, "outcomeForm") || changed;
changed = this.translateNamedId(expression, this.apsFormIndex, "rightOutcomeForm") || changed;
return changed;
boolean changed = false;
ObjectNode expression = (ObjectNode)jsonConditionalSequenceFlow.get("expression");
while (expression != null) {
changed = this.translateNamedId(expression, this.apsFormIndex, "outcomeForm") || changed;
changed = this.translateNamedId(expression, this.apsFormIndex, "rightOutcomeForm") || changed;
expression = (ObjectNode)expression.get("nextCondition");
}
return changed;
}
private boolean translateSubprocesses(ObjectNode jsonDescriptor) {
private boolean translateSubprocesses(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating subprocesses in process: {}", processName);
boolean changed = false;
for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) {
@@ -197,24 +248,30 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
String idField = prefix == null ? "id" : prefix + "Id";
ObjectNode jsonRef = (ObjectNode)_jsonRef;
String modelName = jsonRef.get(nameField).asText();
this.logger.trace("Found model '{}' in the APS Process descriptor", modelName);
if (apsIndex.containsValue(modelName)) {
long modelId = jsonRef.get(idField).asLong();
long apsModelId = apsIndex.getFirstKey(modelName);
if (apsModelId != modelId) {
this.logger.debug("The model '{}' exists in APS with ID {}; leaving unchanged", modelName, apsModelId);
jsonRef.put(idField, apsModelId);
return true;
} else {
this.logger.trace("The model '{}' ID does not change; leaving unchanged", modelName);
}
} else {
this.logger.debug("The model '{}' does not exists in APS; leaving unchanged", modelName);
JsonNode jsonName = jsonRef.get(nameField);
if (jsonName == null) {
this.logger.trace("The expression does not have an APS model name field ({})", nameField);
return false;
}
return false;
String modelName = jsonName.asText();
this.logger.trace("Found model '{}' in the APS Process descriptor", modelName);
if (!apsIndex.containsValue(modelName)) {
this.logger.debug("The model '{}' does not exists in APS; leaving unchanged", modelName);
return false;
}
JsonNode jsonId = jsonRef.get(idField);
long modelId = jsonId == null ? -1L : jsonId.asLong();
long apsModelId = apsIndex.getFirstKey(modelName);
if (apsModelId != modelId) {
this.logger.debug("The model '{}' exists in APS with ID {}", modelName, apsModelId);
jsonRef.put(idField, apsModelId);
return true;
} else {
this.logger.trace("The model '{}' ID does not change; leaving unchanged", modelName);
return false;
}
}

View File

@@ -0,0 +1,74 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* @author brian@inteligr8.com
*/
public class ApsResourceJsonTranslator implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Index<Long, String> apsResourceIndex;
/**
* This constructor initializes the default translator.
*
* @param apsResourceIndex A map of resource IDs to resource names as defined in the APS Service.
*/
public ApsResourceJsonTranslator(Index<Long, String> apsResourceIndex) {
this.apsResourceIndex = apsResourceIndex;
}
@Override
public void translateFile(File file, String formName, Long resourceId) throws IOException {
this.logger.debug("Translating JSON file: {}", file);
boolean changed = false;
ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class);
JsonNode jsonResourceId = jsonDescriptor.get("resourceId");
if (jsonResourceId == null || jsonResourceId.isNull()) {
this.logger.debug("The resource has no ID to translate");
return;
}
this.logger.trace("Found ID {} in the '{}' APS resource descriptor", jsonResourceId, formName);
Long apsResourceId = this.apsResourceIndex.getFirstKey(formName);
if (apsResourceId == null) {
this.logger.debug("The resource '{}' does not exist in APS; leaving unchanged", formName);
} else if (!resourceId.equals(apsResourceId)) {
this.logger.debug("The resource '{}' exists in APS with ID {}; changing descriptor", formName, apsResourceId);
jsonDescriptor.put("resourceId", apsResourceId);
changed = true;
} else {
this.logger.trace("The resource '{}' ID does not change; leaving unchanged", formName, apsResourceId);
}
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, false);
}
}

View File

@@ -0,0 +1,88 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS template JSON configuration file translator.
*
* @author brian@inteligr8.com
*/
public class ApsTemplateJsonTranslator implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsTemplateJsonTranslator.class);
private final Index<Long, String> apsDocumentTemplateIndex;
private final Index<Long, String> apsCustomEmailTemplateIndex;
/**
* This constructor initializes the default translator.
*/
public ApsTemplateJsonTranslator(
Index<Long, String> apsDocumentTemplateIndex,
Index<Long, String> apsCustomEmailTemplateIndex) {
this.apsDocumentTemplateIndex = apsDocumentTemplateIndex;
this.apsCustomEmailTemplateIndex = apsCustomEmailTemplateIndex;
}
@Override
public void translateFile(File file, String _unsued1, Long _unused2) throws IOException {
this.logger.debug("Translating JSON file: {}", file);
boolean changed = false;
ObjectNode json = (ObjectNode) ModelUtil.getInstance().readJson(file);
boolean isDocumentTemplate = json.get("mimeType") != null;
String templateName = json.get("name").asText();
this.logger.trace("Found template name '{}' in APS template file: {}", templateName, file);
Long oldTemplateId = null;
if (json.hasNonNull("id")) {
oldTemplateId = json.get("id").asLong();
this.logger.trace("Found template ID '{}' in APS template file: {}", oldTemplateId, file);
}
Long newTemplateId = null;
if (isDocumentTemplate) {
newTemplateId = this.apsDocumentTemplateIndex.getFirstKey(templateName);
} else {
newTemplateId = this.apsCustomEmailTemplateIndex.getFirstKey(templateName);
}
if (newTemplateId == null) {
// new template; remove the key completely
json.remove("id");
changed = true;
} else if (newTemplateId.equals(oldTemplateId)) {
// unchanged; nothing to do
} else {
json.put("id", newTemplateId);
changed = true;
}
if (changed)
ModelUtil.getInstance().writeJson(json, file, false);
}
}

View File

@@ -0,0 +1,149 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.IOException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsProtectedRestApi;
import com.inteligr8.alfresco.activiti.model.BaseTemplateLight;
import com.inteligr8.alfresco.activiti.model.Tenant;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
import com.inteligr8.maven.aps.modeling.crawler.ApsTemplateCrawlable;
import com.inteligr8.maven.aps.modeling.util.Index;
/**
* This class defines an APS App package translator.
*
* An APS App package is a ZIP file that contains multiple files in a
* predictable folder hierachy with predictable file names. It is effectively
* an APS App interchange format specification.
*
* A package must have at least a single configuration file at the root of the
* ZIP package in the JSON format. It must be named the APS App name. That
* file will then reference all the model elements contained in the ZIP. Any
* model elements not referenced will simply be ignored by APS and by this
* plugin.
*
* This class has methods that provide translator for all the various model
* elements in an APS App package.
*
* @author brian@inteligr8.com
*/
public class ApsTemplateTranslator implements ApsTemplateCrawlable {
private final Logger logger = LoggerFactory.getLogger(ApsTemplateTranslator.class);
private final ApsProtectedRestApi api;
private boolean indexesBuilt = false;
private Index<Long, String> apsDocumentTemplateIndex;
private Index<Long, String> apsCustomEmailTemplateIndex;
public ApsTemplateTranslator(ApsProtectedRestApi api) {
this.api = api;
}
/**
* This method initializes the data required from the APS Service for the
* function of this class.
*
* @throws IOException A network I/O related issue occurred.
*/
public synchronized void buildIndexes() throws IOException {
if (this.indexesBuilt)
return;
this.logger.info("Building indexes ...");
long tenantId = this.findTenantId();
this.logger.debug("APS tenant ID: {}", tenantId);
this.apsDocumentTemplateIndex = this.buildApsDocumentTemplateIndex(tenantId);
this.logLarge("APS document templates: {}", this.apsDocumentTemplateIndex);
this.apsCustomEmailTemplateIndex = this.buildApsCustomEmailTemplateIndex(tenantId);
this.logLarge("APS custom email templates: {}", this.apsCustomEmailTemplateIndex);
this.indexesBuilt = true;
}
private <K, V> void logLarge(String message, Index<K, V> index) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(message, index);
} else {
this.logger.debug(message, index.valueSet());
}
}
@Override
public ApsFileTransformer getTemplateJsonTransformer() {
if (!this.indexesBuilt)
throw new IllegalStateException("The indexes are never built");
return new ApsTemplateJsonTranslator(
this.apsDocumentTemplateIndex,
this.apsCustomEmailTemplateIndex);
}
protected Long findTenantId() {
List<Tenant> tenants = this.api.getAdminApi().getTenants();
return (tenants == null || tenants.isEmpty()) ? null : tenants.iterator().next().getId();
}
protected Index<Long, String> buildApsDocumentTemplateIndex(Long tenantId) {
int perPage = 50;
int page = 1;
ResultList<? extends BaseTemplateLight> templates = this.api.getTemplatesApi().getDocumentTemplates(null, (page-1)*perPage, perPage, "sort_by_name_asc", tenantId);
Index<Long, String> index = new Index<>(templates.getTotal() / 2, false);
while (!templates.getData().isEmpty()) {
this.logger.debug("APS document templates found: {}-{} out of {}", templates.getStart(), (templates.getStart() + templates.getSize()), templates.getTotal());
for (BaseTemplateLight template : templates.getData())
index.put(template.getId(), template.getName());
page++;
templates = this.api.getTemplatesApi().getDocumentTemplates(null, (page-1)*perPage, perPage, "sort_by_name_asc", tenantId);
}
return index;
}
protected Index<Long, String> buildApsCustomEmailTemplateIndex(Long tenantId) {
int perPage = 50;
int page = 1;
ResultList<? extends BaseTemplateLight> templates = this.api.getTemplatesApi().getCustomEmailTemplates(null, (page-1)*perPage, perPage, "sort_by_name_asc", tenantId);
Index<Long, String> index = new Index<>(templates.getTotal() / 2, false);
while (!templates.getData().isEmpty()) {
this.logger.debug("APS document templates found: {}-{} out of {}", templates.getStart(), (templates.getStart() + templates.getSize()), templates.getTotal());
for (BaseTemplateLight template : templates.getData())
index.put(template.getId(), template.getName());
page++;
templates = this.api.getTemplatesApi().getCustomEmailTemplates(null, (page-1)*perPage, perPage, "sort_by_name_asc", tenantId);
}
return index;
}
}

View File

@@ -1,22 +0,0 @@
package com.inteligr8.maven.aps.modeling.util;
import com.fasterxml.jackson.databind.node.ArrayNode;
public class ApsModelArrayNodeSorter extends ArrayNodeObjectSorter {
private static ApsModelArrayNodeSorter INSTANCE = new ApsModelArrayNodeSorter();
public static ApsModelArrayNodeSorter getInstance() {
return INSTANCE;
}
protected ApsModelArrayNodeSorter() {
}
public boolean sort(ArrayNode arrayNode, String jsonObjectFieldName) {
return this.sort(arrayNode, new ObjectNodeComparator(jsonObjectFieldName));
}
}

View File

@@ -1,23 +0,0 @@
package com.inteligr8.maven.aps.modeling.util;
import java.util.List;
import java.util.Map;
public class ApsModelListMapSorter extends ListMapSorter<String, Object> {
private static ApsModelListMapSorter INSTANCE = new ApsModelListMapSorter();
public static ApsModelListMapSorter getInstance() {
return INSTANCE;
}
protected ApsModelListMapSorter() {
}
public boolean sort(List<Map<String, Object>> jsonArrayAsMap, String jsonObjectFieldName) {
return this.sort(jsonArrayAsMap, new MapComparator<String, Object>(jsonObjectFieldName));
}
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.util.Comparator;
@@ -5,10 +19,18 @@ import java.util.Comparator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
/**
* A singleton class that provides JSON array sorting methods.
*
* @author brian@inteligr8.com
*/
public class ArrayNodeObjectSorter {
private static ArrayNodeObjectSorter INSTANCE = new ArrayNodeObjectSorter();
/**
* @return A singleton instance of this class.
*/
public static ArrayNodeObjectSorter getInstance() {
return INSTANCE;
}
@@ -17,7 +39,33 @@ public class ArrayNodeObjectSorter {
protected ArrayNodeObjectSorter() {
}
/**
* This method sorts the JSON object elements of the JSON array by the
* value of the specified field in the JSON objects.
*
* If a JSON object has the specified field, its value is subject to the
* standard comparison. If it doesn't have a value, then it will sort to
* the end of the array.
*
* @see ObjectNodeComparator
*
* @param arrayNode A JSON array of JSON objects.
* @param jsonObjectFieldName A field in the JSON object elements.
* @return true if any elements were moved/sorted; false if unchanged.
*/
public boolean sort(ArrayNode arrayNode, String jsonObjectFieldName) {
return this.sort(arrayNode, new ObjectNodeComparator(jsonObjectFieldName));
}
/**
* This method sorts the JSON node elements of the JSON array using the
* specified comparator.
*
* @param arrayNode A JSON array of JSON objects.
* @param comparator A JSON node comparator.
* @return true if any elements were moved/sorted; false if unchanged.
*/
public boolean sort(ArrayNode arrayNode, Comparator<JsonNode> comparator) {
if (arrayNode.isEmpty())
return false;

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.util.HashMap;
@@ -9,56 +23,122 @@ import java.util.Set;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* A class that implements bi-directional map for forward and reverse indexing
* and lookups.
*
* The index is considered to be many-to-1. So the forward index appears as
* 1-to-1 (one key to one value) and the reverse index appears as 1-to-many
* (one value to many keys).
*
* If the index should be considered to be 1-to-1, then set
* `allowDuplicateValues` to `false`.
*
* @author brian@inteligr8.com
* @param <K> An index key type.
* @param <V> An index value type.
*/
public class Index<K, V> {
private final Map<K, V> forwardMap;
private final Map<V, Set<K>> reverseMap;
private final boolean allowDuplicateValues;
/**
* This constructor defines a new index with some meta-data.
*
* The capacity is not strictly enforced. It should be accurate for
* performance reasons.
*
* @param initialCapacity A capacity, as you would specify in an ArrayList or HashMap.
* @param allowDuplicateValues true to allow multiple keys to have the same value; false to throw an IllegalArgumentException.
*/
public Index(int initialCapacity, boolean allowDuplicateValues) {
this.forwardMap = new HashMap<K, V>(initialCapacity);
this.reverseMap = new HashMap<V, Set<K>>(initialCapacity);
this.allowDuplicateValues = allowDuplicateValues;
}
/**
* @param key An index key.
* @return true if it exists; false otherwise.
*/
public boolean containsKey(K key) {
return this.forwardMap.containsKey(key);
}
/**
* @param value An indexed value.
* @return true if it exists; false otherwise.
*/
public boolean containsValue(V value) {
return this.reverseMap.containsKey(value);
}
/**
* @param key An index key.
* @return The value; null if key not indexed.
*/
public V getValue(K key) {
return this.forwardMap.get(key);
}
/**
* @return A set of all keys; may be empty; never null.
*/
public Set<K> keySet() {
return this.forwardMap.keySet();
}
/**
* @return A set of all values; may be empty; never null.
*/
public Set<V> valueSet() {
return this.reverseMap.keySet();
}
/**
* @param value An indexed value.
* @return A set of keys; never empty; null if value not indexed.
*/
public Set<K> getKeys(V value) {
return this.reverseMap.get(value);
}
/**
* @param value An indexed value.
* @return The key; null if value not indexed.
*/
public K getFirstKey(V value) {
Set<K> keys = this.reverseMap.get(value);
return (keys == null || keys.isEmpty()) ? null : keys.iterator().next();
}
/**
* @return The number of keys in the index.
*/
public int size() {
return this.forwardMap.size();
}
/**
* @return Interable access to the entries of the forward index.
*/
public Iterable<Entry<K, V>> entries() {
return this.forwardMap.entrySet();
}
/**
* @param key An index key.
* @param value A value to index to the specified key.
* @return true if the key already existed and was overwritten; false otherwise.
*/
public synchronized boolean put(K key, V value) {
if (key == null)
throw new IllegalArgumentException("An index key may not be null");
if (value == null)
throw new IllegalArgumentException("An index value may not be null");
boolean overwrote = false;
V oldValue = this.forwardMap.get(key);
@@ -81,7 +161,14 @@ public class Index<K, V> {
return overwrote;
}
/**
* @param key An index key.
* @return true if the key and its value were removed; false otherwise.
*/
public synchronized boolean remove(K key) {
if (key == null)
throw new IllegalArgumentException("An index key may not be null");
V oldValue = this.forwardMap.remove(key);
if (oldValue == null)
return false;
@@ -91,6 +178,9 @@ public class Index<K, V> {
return true;
}
/**
* This method clears/resets the index.
*/
public synchronized void clear() {
this.forwardMap.clear();
this.reverseMap.clear();

View File

@@ -1,12 +1,73 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class ListMapSorter<K, V> {
/**
* A singleton class that provides list of maps sorting methods.
*
* @author brian@inteligr8.com
*/
public class ListMapSorter {
public boolean sort(List<Map<K, V>> listOfMaps, Comparator<Map<K, V>> comparator) {
private static ListMapSorter INSTANCE = new ListMapSorter();
/**
* @return A singleton instance of this class.
*/
public static ListMapSorter getInstance() {
return INSTANCE;
}
protected ListMapSorter() {
}
/**
* This method sorts the maps in the list by the value of the specified key
* in the maps.
*
* If a map has the specified key, its value is subject to the standard
* comparison. If it doesn't have a value, then it will sort to the end of
* the array.
*
* @see MapComparator
*
* @param <V> The type of value in the maps.
* @param listOfMaps A list of maps.
* @param mapKey A key in the maps.
* @return true if any maps were moved/sorted; false if unchanged.
*/
public <V> boolean sort(List<Map<String, V>> listOfMaps, String mapKey) {
return this.sort(listOfMaps, new MapComparator<String, V>(mapKey));
}
/**
* This method sorts the maps of the list using the specified comparator.
*
* @param <K> The type of key in the maps.
* @param <V> The type of value in the maps.
* @param listOfMaps A list of maps.
* @param comparator A map comparator.
* @return true if any maps were moved/sorted; false if unchanged.
*/
public <K, V> boolean sort(List<Map<K, V>> listOfMaps, Comparator<Map<K, V>> comparator) {
if (listOfMaps.isEmpty())
return false;

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.util.Arrays;
@@ -5,15 +19,43 @@ import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* This class implements a comparator for the value of map keys.
*
* If key values are considered equal, fallback keys are supported. Any map
* without the key or not null value will be considered "greater than" ones
* that have values for the specified key. This will move them to the end of
* any sorted list.
*
* @author brian@inteligr8.com
* @param <K> An map key type.
* @param <V> An map value type.
*/
public class MapComparator<K, V> implements Comparator<Map<K, V>> {
//private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class);
private final List<K> keys;
/**
* An array-based constructor.
*
* The class will sort on the values of these map keys, with a priority in
* the specified order.
*
* @param keys An array of keys.
*/
public MapComparator(@SuppressWarnings("unchecked") K... keys) {
this(Arrays.asList(keys));
}
/**
* A list-based constructor.
*
* The class will sort on the values of these map keys, with a priority in
* the specified order.
*
* @param keys A list of keys.
*/
public MapComparator(List<K> keys) {
this.keys = keys;
}

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.io.BufferedInputStream;
@@ -38,21 +52,31 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.inteligr8.maven.aps.modeling.xml.DomNamespaceContext;
/**
* This singleton class provides JSON and XML I/O tools and consistent formatting.
*
* @author brian@inteligr8.com
*/
public class ModelUtil {
public final static QName CDATA_FLEX = QName.valueOf("inteligr8:xml:cdata_or_element");
private final static ModelUtil INSTANCE = new ModelUtil();
/**
* @return A singleton instance of this class.
*/
public static ModelUtil getInstance() {
return INSTANCE;
}
private final ObjectMapper om = new ObjectMapper();
private final ObjectMapper omsorted = new ObjectMapper();
private final ObjectMapper om = new ObjectMapper().registerModule(new JavaTimeModule());
private final ObjectMapper omsorted = new ObjectMapper().registerModule(new JavaTimeModule());
private final DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
private final DocumentBuilder docBuilder;
private final XPathFactory xpathfactory = XPathFactory.newInstance();
@@ -89,11 +113,42 @@ public class ModelUtil {
/**
* This method reads/parses JSON and immediately formats/writes JSON back
* to the specified file.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* This method will load the full set of JSON objects into memory.
*
* @param inplaceFile A file handle to an existing valid JSON file.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void json(File inplaceFile, boolean enableSorting) throws IOException {
JsonNode json = this.readJson(inplaceFile);
this.writeJson(json, inplaceFile, enableSorting);
}
/**
* This method reads/parses JSON from the specified source file and
* immediately formats/writes JSON to the specified target file.
*
* If the specified target file already exists, it will be overwritten.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* This method will load the full set of JSON objects into memory.
*
* @param sourceFile A file handle to an existing valid JSON file.
* @param targetFile A file handle for the target JSON file.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void json(File sourceFile, File targetFile, boolean enableSorting) throws IOException {
FileInputStream fistream = new FileInputStream(sourceFile);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -110,16 +165,46 @@ public class ModelUtil {
}
}
/**
* This method reads/parses JSON from the specified source stream and
* immediately formats/writes JSON to the specified target stream.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* This method will load the full set of JSON objects into memory.
*
* @param istream An input stream to valid JSON.
* @param ostream An output stream.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A stream I/O issue occurred.
*/
public void json(InputStream istream, OutputStream ostream, boolean enableSorting) throws IOException {
// FIXME stream it for lower memory/IO usage
JsonNode json = this.readJson(istream);
this.writeJson(json, ostream, enableSorting);
}
/**
* This method reads/parses JSON from the specified file.
*
* @param file A file handle to an existing valid JSON file.
* @return A JSON node (array, object, or value).
* @throws IOException A file I/O issue occurred.
*/
public JsonNode readJson(File file) throws IOException {
return this.readJson(file, JsonNode.class);
}
/**
* This method reads/parses JSON from the specified file.
*
* @param <T> The class of the type to parse.
* @param file A file handle to an existing valid JSON file.
* @param type A type parse the JSON into (using the Jackson parser).
* @return An instance of the specified type.
* @throws IOException A file I/O issue occurred.
*/
public <T> T readJson(File file, Class<T> type) throws IOException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -130,6 +215,14 @@ public class ModelUtil {
}
}
/**
* This method reads/parses JSON from the specified file.
*
* @param <T> The class of the type to parse.
* @param file A file handle to an existing valid JSON file.
* @return A JSON array as a list.
* @throws IOException A file I/O issue occurred.
*/
public <T> List<T> readJsonAsList(File file) throws IOException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -140,6 +233,13 @@ public class ModelUtil {
}
}
/**
* This method reads/parses JSON from the specified file.
*
* @param file A file handle to an existing valid JSON file.
* @return A JSON object as a map.
* @throws IOException A file I/O issue occurred.
*/
public Map<String, Object> readJsonAsMap(File file) throws IOException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -150,24 +250,90 @@ public class ModelUtil {
}
}
/**
* This method reads/parses JSON from the specified stream.
*
* @param istream An input stream to valid JSON.
* @return A JSON node (array, object, or value).
* @throws IOException A stream I/O issue occurred.
*/
public JsonNode readJson(InputStream istream) throws IOException {
return this.om.readTree(istream);
}
/**
* This method reads/parses JSON from the specified stream.
*
* @param <T> The class of the type to parse.
* @param istream An input stream to valid JSON.
* @param type A type parse the JSON into (using the Jackson parser).
* @return An instance of the specified type.
* @throws IOException A stream I/O issue occurred.
*/
public <T> T readJson(InputStream istream, Class<T> type) throws IOException {
return this.om.readValue(istream, type);
}
/**
* This method reads/parses JSON from the specified stream.
*
* @param <T> The class of the type to parse.
* @param istream An input stream to valid JSON.
* @return A JSON array as a list.
* @throws IOException A stream I/O issue occurred.
*/
@SuppressWarnings("unchecked")
public <T> List<T> readJsonAsList(InputStream istream) throws IOException {
return this.om.readValue(istream, List.class);
}
/**
* This method reads/parses JSON from the specified stream.
*
* @param istream An input stream to valid JSON.
* @return A JSON object as a map.
* @throws IOException A stream I/O issue occurred.
*/
@SuppressWarnings("unchecked")
public Map<String, Object> readJsonAsMap(InputStream istream) throws IOException {
return this.om.readValue(istream, Map.class);
}
/**
* This method reads/parses a Java POJO.
*
* @param o A Java POJO.
* @return A JSON node (array, object, or value).
*/
public ObjectNode readPojo(Object o) {
return this.om.convertValue(o, ObjectNode.class);
}
/**
* This method reads/parses a Java POJO.
*
* @param o A Java POJO.
* @return A Java POJO as a map.
*/
@SuppressWarnings("unchecked")
public Map<String, Object> readPojoAsMap(Object o) {
return this.om.convertValue(o, Map.class);
}
/**
* This method formats/writes JSON to the specified file.
*
* If the specified file already exists, it will be overwritten.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* @param json A JSON node (array, object, or value).
* @param file A file handle.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void writeJson(JsonNode json, File file, boolean enableSorting) throws IOException {
FileOutputStream fostream = new FileOutputStream(file);
BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize);
@@ -178,6 +344,20 @@ public class ModelUtil {
}
}
/**
* This method formats/writes JSON to the specified file.
*
* If the specified file already exists, it will be overwritten.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* @param map A Java map.
* @param file A file handle.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void writeJson(Map<String, Object> map, File file, boolean enableSorting) throws IOException {
FileOutputStream fostream = new FileOutputStream(file);
BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize);
@@ -188,6 +368,18 @@ public class ModelUtil {
}
}
/**
* This method formats/writes JSON to the specified file.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* @param json A JSON node (array, object, or value).
* @param ostream An output stream.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void writeJson(JsonNode json, OutputStream ostream, boolean enableSorting) throws IOException {
if (enableSorting) {
this.omsorted.writeValue(ostream, json);
@@ -196,6 +388,18 @@ public class ModelUtil {
}
}
/**
* This method formats/writes JSON to the specified file.
*
* You many optionally enable the sorting of elements. Sorting makes the
* files more diff-friendly. The algorithm will sort JSON objects by keys.
* JSON array values will not be sorted.
*
* @param map A Java map.
* @param ostream An output stream.
* @param enableSorting true to sort; false to leave as-is.
* @throws IOException A file I/O issue occurred.
*/
public void writeJson(Map<String, Object> map, OutputStream ostream, boolean enableSorting) throws IOException {
if (enableSorting) {
this.omsorted.writeValue(ostream, map);
@@ -203,9 +407,33 @@ public class ModelUtil {
this.om.writeValue(ostream, map);
}
}
/**
* This method formats/writes a Java POJO of the specified type using the
* specified map.
*
* @param <T> The class of the type to create.
* @param map A Java map.
* @param type A Java class to create.
* @return A Java POJO instance.
*/
public <T> T writePojo(Map<String, Object> map, Class<T> type) {
return this.om.convertValue(map, type);
}
/**
* This method reads/parses XML and immediately formats/writes XML back to
* the specified file.
*
* This method will load the full DOM of XML into memory.
*
* @param inplaceFile A file handle to an existing valid XML file.
* @throws IOException A file I/O issue occurred.
* @throws SAXException The file is not valid XML.
* @throws TransformerException An unknown XML transformation issue occurred.
*/
public void xml(File inplaceFile) throws IOException, SAXException, TransformerException {
Document xml;
@@ -226,6 +454,20 @@ public class ModelUtil {
}
}
/**
* This method reads/parses XML from the specified source file and
* immediately formats/writes XML to to the specified target file.
*
* If the specified target file already exists, it will be overwritten.
*
* This method will load the full DOM of XML into memory.
*
* @param sourceFile A file handle to an existing valid XML file.
* @param targetFile A file handle.
* @throws IOException A file I/O issue occurred.
* @throws SAXException The file is not valid XML.
* @throws TransformerException An unknown XML transformation issue occurred.
*/
public void xml(File sourceFile, File targetFile) throws IOException, SAXException, TransformerException {
FileInputStream fistream = new FileInputStream(sourceFile);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -242,12 +484,31 @@ public class ModelUtil {
}
}
/**
* This method reads/parses XML from the specified source stream and
* immediately formats/writes XML to to the specified target stream.
*
* This method will load the full DOM of XML into memory.
*
* @param istream An input stream to valid XML.
* @param ostream An output stream.
* @throws IOException A stream I/O issue occurred.
* @throws SAXException The stream is not valid XML.
* @throws TransformerException An unknown XML transformation issue occurred.
*/
public void xml(InputStream istream, OutputStream ostream) throws IOException, SAXException, TransformerException {
// FIXME stream it for lower memory/IO usage
Document doc = this.readXml(istream);
this.writeXml(doc, ostream);
}
/**
* This method reads/parses XML from the specified file.
*
* @param file A file handle to an existing valid XML file.
* @return An XML DOM Document object.
* @throws IOException A file I/O issue occurred.
* @throws SAXException The file is not valid XML.
*/
public Document readXml(File file) throws IOException, SAXException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
@@ -258,10 +519,28 @@ public class ModelUtil {
}
}
/**
* This method reads/parses XML from the specified stream.
*
* @param istream An input stream to valid XML.
* @return An XML DOM Document object.
* @throws IOException A stream I/O issue occurred.
* @throws SAXException The stream is not valid XML.
*/
public Document readXml(InputStream istream) throws IOException, SAXException {
return this.docBuilder.parse(istream);
}
/**
* This method formats/writes XML to the specified file.
*
* If the specified file already exists, it will be overwritten.
*
* @param xml An XML element.
* @param file A file handle.
* @throws IOException A file I/O issue occurred.
* @throws TransformerException An unknown XML transformation issue occurred.
*/
public void writeXml(Node xml, File file) throws IOException, TransformerException {
FileOutputStream fostream = new FileOutputStream(file);
BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize);
@@ -272,16 +551,43 @@ public class ModelUtil {
}
}
/**
* This method formats/writes XML to the specified stream.
*
* @param xml An XML element.
* @param ostream An output stream.
* @throws IOException A stream I/O issue occurred.
* @throws TransformerException An unknown XML transformation issue occurred.
*/
public void writeXml(Node xml, OutputStream ostream) throws IOException, TransformerException {
DOMSource source = new DOMSource(xml);
StreamResult result = new StreamResult(ostream);
this.transformer.transform(source, result);
}
/**
* This method searches the DOM using an XPath expression.
*
* @param node An XML node.
* @param xpathExpr An XPath expression.
* @return An XML DOM list of nodes object; null if no result.
* @throws XPathExpressionException An unknown XPath issue occurred.
*/
public NodeList xpath(Node node, String xpathExpr) throws XPathExpressionException {
return (NodeList)this.xpath(node, xpathExpr, XPathConstants.NODESET);
}
/**
* This method searches the DOM using an XPath expression.
*
* @param node An XML node.
* @param xpathExpr An XPath expression.
* @param returnType An XPath return type: see XPathConstants.
* @return An XML DOM list of nodes object; null if no result.
* @throws XPathExpressionException An unknown XPath issue occurred.
*/
public Object xpath(Node node, String xpathExpr, QName returnType) throws XPathExpressionException {
XPath xpath = this.xpathfactory.newXPath();
xpath.setNamespaceContext(new DomNamespaceContext(node));

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.util;
import java.util.Arrays;
@@ -6,15 +20,45 @@ import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
/**
* This class implements a comparator for the value of JSON object fields.
*
* If field values are considered equal, fallback fields are supported. Any
* object without the field in question will be considered "greater than" ones
* that have the specified field. This will move them to the end of any sorted
* list.
*
* If a JSON value or array is encountered instead of a JSON object, then it is
* treated exactly like a JSON object without the specified field. It will be
* considered "greater than" and move to the end of any sorted list.
*
* @author brian@inteligr8.com
*/
public class ObjectNodeComparator implements Comparator<JsonNode> {
//private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class);
private final List<String> objectFieldNames;
/**
* An array-based constructor.
*
* The class will sort on the values of these JSON object field names, with
* a priority in the specified order.
*
* @param objectFieldNames An array of JSON object field names.
*/
public ObjectNodeComparator(String... objectFieldNames) {
this(Arrays.asList(objectFieldNames));
}
/**
* A list-based constructor.
*
* The class will sort on the values of these JSON object field names, with
* a priority in the specified order.
*
* @param objectFieldNames A list of JSON object field names.
*/
public ObjectNodeComparator(List<String> objectFieldNames) {
this.objectFieldNames = objectFieldNames;
}
@@ -22,9 +66,9 @@ public class ObjectNodeComparator implements Comparator<JsonNode> {
@Override
public int compare(JsonNode o1, JsonNode o2) {
if (!o1.isObject())
return -1;
if (!o2.isObject())
return 1;
if (!o2.isObject())
return -1;
for (String objectFieldName : objectFieldNames) {
JsonNode value1 = o1.get(objectFieldName);

View File

@@ -1,3 +1,17 @@
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.inteligr8.maven.aps.modeling.xml;
import java.util.Collections;
@@ -9,6 +23,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
/**
* This class implements namespace handling for DOM processing.
*/
public class DomNamespaceContext implements NamespaceContext {
private final Logger logger = LoggerFactory.getLogger(DomNamespaceContext.class);
@@ -16,10 +33,22 @@ public class DomNamespaceContext implements NamespaceContext {
private final String defaultNamespacePrefix;
private final String defaultNamespace;
/**
* The constructor using just a DOM node. The default namespace prefix is
* assumed to be 'tns'.
*
* @param node A DOM node.
*/
public DomNamespaceContext(Node node) {
this(node, "tns");
}
/**
* The constructor using just a DOM node.
*
* @param node A DOM node.
* @param defaultNamespacePrefix A DOM namespace prefix.
*/
public DomNamespaceContext(Node node, String defaultNamespacePrefix) {
this.node = node;
this.defaultNamespacePrefix = defaultNamespacePrefix;