49 Commits

Author SHA1 Message Date
9e62c994ec v1.4.1 pom 2023-11-16 09:31:27 -05:00
6041898be3 Merge branch 'develop' into stable 2023-11-16 09:26:45 -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
ceb448c382 Merge branch 'develop' into stable 2023-11-15 17:01:48 -05:00
8bb15894e8 updated javadocs 2023-11-15 17:01:39 -05:00
c7e0d48908 v1.4.0 pom 2023-11-15 16:29:32 -05:00
8b478e35d7 Merge branch 'develop' into stable 2023-11-15 16:29:13 -05:00
bd5b218509 added initial template support 2023-11-15 16:28:21 -05:00
1d5fa40209 v1.3.6 pom 2023-07-07 15:24:06 -04:00
ab0c27eab1 Merge branch 'develop' into stable 2023-07-07 15:23:23 -04:00
55d85814ab updated APS model/client libs 2023-07-07 15:22:59 -04:00
c2f96d36f6 v1.3.5 pom 2023-05-28 13:34:48 -04:00
05fc9e7db9 Merge branch 'develop' into stable 2023-05-28 13:34:23 -04:00
78b7fc2ec9 updated to latest APS API/client 2023-05-28 13:34:10 -04:00
d3aa1dd0bf v1.3.4 pom 2022-11-18 15:43:53 -05:00
3fca20385e Merge branch 'develop' into stable 2022-11-18 15:41:35 -05:00
63ffd77622 fixed NPE on expressions 2022-11-18 15:40:50 -05:00
ad74bf02a3 v1.3.3 pom 2022-11-18 15:10:57 -05:00
99d9cc8d17 Merge branch 'develop' into stable 2022-11-18 14:01:59 -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
47abf6cce4 v1.3.2 pom 2022-10-10 22:26:51 -04:00
c789361253 Merge branch 'develop' into stable 2022-10-10 22:26:12 -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
a2993af681 v1.3.1 pom 2022-06-03 09:03:02 -04:00
8eb63c4d53 Merge branch 'develop' into stable 2022-06-03 08:55:50 -04:00
dd9d5735bf fixed user-based translation 2022-06-03 13:53:05 +01:00
14b8645f54 v1.3.0 pom 2022-05-13 22:41:59 +01:00
57e3579c37 Merge branch 'develop' into stable 2022-05-13 22:41:36 +01:00
17594dc520 added publish-app goal 2022-05-13 22:39:46 +01:00
adb43d46b0 fixed NPE for share-model 2022-05-13 22:39:37 +01:00
a2befd7dbd rename deploy to upload; fixed for non-existent apps 2022-05-13 22:39:10 +01:00
b2fbe41fe1 v1.3-SNAPSHOT pom 2022-05-13 22:37:14 +01:00
1eb3376c80 v1.2.1 pom 2022-05-03 17:17:55 -04:00
720c83c230 Merge branch 'develop' into stable 2022-05-03 17:17:10 -04:00
d5c31527a4 added share-models goal 2022-05-03 22:16:04 +01:00
b75448335e v1.2.0 pom 2022-05-03 10:57:58 -04:00
3d174119d4 Merge branch 'develop' into stable 2022-05-03 10:56:51 -04:00
32d4e807a1 automatically adding missing organizations 2022-05-03 15:55:24 +01:00
0c9dff2e23 updated aps-public-rest projects 2022-05-03 13:46:48 +01:00
54 changed files with 3741 additions and 451 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` | |

203
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.1.0</version>
<version>1.4.1</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>
@@ -34,36 +44,25 @@
<maven.compiler.target>1.8</maven.compiler.target>
<maven.version>3.6.3</maven.version>
<jersey.version>2.34</jersey.version>
<jersey.version>2.39.1</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-api</artifactId>
<version>1.2.3</version>
<classifier>aps1</classifier>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-client</artifactId>
<version>2.0.5-jersey</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-proxy-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
@@ -118,10 +117,36 @@
</dependencies>
<build>
<pluginManagement>
<plugins>
<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>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.0</version>
<version>3.6.1</version>
<configuration>
<goalPrefix>aps-model</goalPrefix>
</configuration>
@@ -152,52 +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>
</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>
@@ -209,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>
@@ -228,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.6.13</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

@@ -0,0 +1,34 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin-aps-info</artifactId>
<version>@pom.version@</version>
<packaging>pom</packaging>
<name>APS Share Models Plugin Tests</name>
<build>
<plugins>
<plugin>
<groupId>${project.groupId}</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>@pom.version@</version>
<executions>
<execution>
<id>share-models</id>
<phase>validate</phase>
<goals>
<goal>share-models</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -6,11 +6,11 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin-deploy-app</artifactId>
<artifactId>aps-model-maven-plugin-upload-app</artifactId>
<version>@pom.version@</version>
<packaging>pom</packaging>
<name>Deploy App Plugin Tests</name>
<name>Upload App Plugin Tests</name>
<build>
<plugins>
@@ -53,10 +53,10 @@
</configuration>
</execution>
<execution>
<id>deploy-app</id>
<id>upload-app</id>
<phase>package</phase>
<goals>
<goal>deploy-app</goal>
<goal>upload-app</goal>
</goals>
<configuration>
<zipDirectory>${project.build.directory}/aps-test</zipDirectory>

View File

@@ -1,13 +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.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();
}

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 ...");
@@ -83,7 +115,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,38 @@
/*
* 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.plugins.annotations.Parameter;
import org.apache.maven.settings.Server;
import com.inteligr8.alfresco.activiti.ApsClientConfiguration;
import com.inteligr8.alfresco.activiti.ApsClientJerseyConfiguration;
import com.inteligr8.alfresco.activiti.ApsClientJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
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 )
@@ -36,9 +61,17 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
private ApsPublicRestApiJerseyImpl api;
public ApsClientConfiguration 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() {
this.getLog().debug("Configuring APS to URL: " + this.activitiAppBaseUrl);
ApsClientConfiguration config = new ApsClientConfiguration();
ApsClientJerseyConfiguration config = new ApsClientJerseyConfiguration();
config.setBaseUrl(this.activitiAppBaseUrl);
switch (this.activitiAppAuthType.toUpperCase()) {
case "BASIC":
@@ -86,14 +119,34 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
return config;
}
/**
* This method constructs and caches the APS API accessor.
*
* @return An APS API instance.
*/
public synchronized ApsPublicRestApiJerseyImpl getApsApi() {
if (this.api == null) {
ApsClientConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config);
this.api = new ApsPublicRestApiJerseyImpl(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.
*/
protected Long findTenantId() {
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() throws MojoExecutionException {
return this.findAppIdByName(this.apsAppName);
}
protected Long findAppIdByName(String appName) throws MojoExecutionException {
Map<String, Long> apps = this.findAppNameIds();
Long appId = apps.get(this.apsAppName);
if (appId == null)
throw new MojoExecutionException("The APS App '' could not be found; valid apps: " + apps.keySet());
return appId;
}
}

View File

@@ -0,0 +1,118 @@
/*
* 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.List;
import java.util.Map;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Parameter;
import com.inteligr8.activiti.model.Datum;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.Tenant;
/**
* 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 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.
*/
protected Long findTenantId() {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
if (tenants == null || tenants.isEmpty())
return null;
return tenants.iterator().next().getId();
}
/**
* 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.
*/
protected Map<String, ModelRepresentation> buildAppNameMap() {
ApsPublicRestApiJerseyImpl 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

@@ -0,0 +1,235 @@
/*
* 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;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.activiti.model.Datum;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.api.ModelsApi;
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.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 {
@Parameter( property = "aps-model.share.modelName" )
protected String modelName;
@Parameter( property = "aps-model.share.readers" )
protected String readers;
@Parameter( property = "aps-model.share.editors" )
protected String editors;
@Parameter( property = "aps-model.share.app.readers" )
protected String appReaders;
@Parameter( property = "aps-model.share.app.editors" )
protected String appEditors;
@Parameter( property = "aps-model.share.process.readers" )
protected String processReaders;
@Parameter( property = "aps-model.share.process.editors" )
protected String processEditors;
@Parameter( property = "aps-model.share.form.readers" )
protected String formReaders;
@Parameter( property = "aps-model.share.form.editors" )
protected String formEditors;
@Parameter( property = "aps-model.share.doRevoke", defaultValue = "false" )
protected boolean doRevoke = false;
protected Set<String> appReaderSet;
protected Set<String> appEditorSet;
protected Set<String> processReaderSet;
protected Set<String> processEditorSet;
protected Set<String> formReaderSet;
protected Set<String> formEditorSet;
private Index<String, Long> identityIndex = new Index<>(128, true);
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.normalizeParameters();
this.buildIdentityIndex();
if (!this.appReaderSet.isEmpty() || !this.appEditorSet.isEmpty())
this.shareModels(ModelsApi.ModelType.App, this.appReaderSet, this.appEditorSet);
if (!this.processReaderSet.isEmpty() || !this.processEditorSet.isEmpty())
this.shareModels(ModelsApi.ModelType.Process, this.processReaderSet, this.processEditorSet);
if (!this.formReaderSet.isEmpty() || !this.formEditorSet.isEmpty())
this.shareModels(ModelsApi.ModelType.Form, this.formReaderSet, this.formEditorSet);
}
private void shareModels(ModelsApi.ModelType modelType, Set<String> readers, Set<String> editors) {
ResultList<ModelRepresentation> models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
if (models.getData() == null)
return;
for (ModelRepresentation model : models.getData()) {
if (this.modelName != null && !this.modelName.equals(model.getName()))
continue;
Set<String> groupsAddressed = new HashSet<>();
Set<String> readersUnaddressed = new HashSet<>(readers);
Set<String> editorsUnaddressed = new HashSet<>(editors);
ShareInfoRequest changeRequest = new ShareInfoRequest();
ResultList<SharePermission> shares = this.getApsApi().getShareApi().getShareInfo(model.getId().toString());
if (shares.getData() != null) {
for (SharePermission share : shares.getData()) {
if (share.getGroup() != null) {
groupsAddressed.add(share.getGroup().getName());
this.analyzeChanges(modelName, readers, editors, share, changeRequest);
} else if (share.getPerson() != null) {
this.getLog().debug("Person-based model sharing not supported at this time; ignoring");
}
}
}
readersUnaddressed.removeAll(groupsAddressed);
for (String reader : readersUnaddressed)
this.analyzeChanges(modelName, reader, PermissionLevel.Read, changeRequest);
editorsUnaddressed.removeAll(groupsAddressed);
for (String editor : editorsUnaddressed)
this.analyzeChanges(modelName, editor, PermissionLevel.Write, changeRequest);
if (!changeRequest.getAdded().isEmpty() || !changeRequest.getUpdated().isEmpty() || !changeRequest.getRemoved().isEmpty()) {
this.getLog().info("Sharing model: " + modelType + " => '" + modelName + "'");
this.getApsApi().getShareApi().setShareInfo(model.getId().toString(), changeRequest);
}
}
}
private void analyzeChanges(String modelName, Set<String> readers, Set<String> editors, SharePermission share, ShareInfoRequest changeRequest) {
if (PermissionLevel.Write.equals(share.getPermission())) {
if (editors.contains(share.getGroup().getName())) {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an editor of model '" + modelName + "'");
// no change
return;
} else if (readers.contains(share.getGroup().getName())) {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' reverting from editor to reader of model '" + modelName + "'");
changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Read));
return;
}
} else {
if (editors.contains(share.getGroup().getName())) {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' elevating from reader to editor of model '" + modelName + "'");
changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Write));
return;
} else if (readers.contains(share.getGroup().getName())) {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an reader of model '" + modelName + "'");
// no change
return;
}
}
if (this.doRevoke) {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'; revoking ...");
changeRequest.getRemoved().add(new PermissionLight().withId(share.getId()));
} else {
this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'");
// not touching extra unnamed permissions
}
}
private void analyzeChanges(String modelName, String groupName, PermissionLevel plevel, ShareInfoRequest changeRequest) {
Long groupId = this.identityIndex.getValue(groupName);
if (groupId == null) {
this.getLog().warn("The named group '" + groupName + "' does not exist in APS; ignoring ...");
} else {
this.getLog().debug("The named group '" + groupName + "' granted the " + plevel + " role of model '" + modelName + "'");
changeRequest.getAdded().add(new PermissionLight().withGroupId(groupId).withPermission(plevel));
}
}
protected void normalizeParameters() {
Set<String> readerSet = this.normalizeParameter(this.readers);
Set<String> editorSet = this.normalizeParameter(this.editors);
this.appReaderSet = this.normalizeParameter(this.appReaders, readerSet);
this.appEditorSet = this.normalizeParameter(this.appEditors, editorSet);
this.processReaderSet = this.normalizeParameter(this.processReaders, readerSet);
this.processEditorSet = this.normalizeParameter(this.processEditors, editorSet);
this.formReaderSet = this.normalizeParameter(this.formReaders, readerSet);
this.formEditorSet = this.normalizeParameter(this.formEditors, editorSet);
}
private Set<String> normalizeParameter(String parameter, Collection<String> c) {
Set<String> set = this.normalizeParameter(parameter);
set.addAll(c);
return set;
}
private Set<String> normalizeParameter(String parameter) {
Set<String> params = new HashSet<>();
if (parameter == null)
return params;
if (parameter.length() == 0)
return params;
String[] splitParams = parameter.split(",");
params.addAll(Arrays.asList(splitParams));
return params;
}
protected void buildIdentityIndex() {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
for (Tenant tenant : tenants) {
List<GroupLight> groups = this.getApsApi().getAdminApi().getGroups(tenant.getId(), true, true);
this.getLog().debug("Indexing groups: " + groups.size());
for (GroupLight group : groups)
this.identityIndex.put(group.getName(), group.getId());
if (this.getLog().isDebugEnabled())
this.getLog().debug("Indexed groups: " + this.identityIndex.toString());
}
}
}

View File

@@ -0,0 +1,162 @@
/*
* 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.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() {
Long tenantId = this.findTenantId();
return this.findTemplates(tenantId);
}
protected Map<TemplateType, Map<String, ? extends BaseTemplateLight>> findTemplates(Long tenantId) {
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) {
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) {
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) {
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,98 +0,0 @@
package com.inteligr8.maven.aps.modeling.goal;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
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.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
@Mojo( name = "deploy-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class DeployAppGoal extends ApsAppAccessibleGoal {
@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;
protected final int bufferSize = 128 * 1024;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
File sourceFile = this.validateSourceDirectory();
Long appId = this.findAppId();
try {
this.uploadApp(appId, sourceFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be uploaded", ie);
}
}
protected File validateSourceDirectory() {
if (!this.zipDirectory.exists()) {
throw new IllegalStateException("The 'zipDirectory' does not exist: " + this.zipDirectory);
} else if (!this.zipDirectory.isDirectory()) {
throw new IllegalStateException("The 'zipDirectory' is not a directory: " + this.zipDirectory);
}
File sourceFile = new File(this.zipDirectory, this.apsAppName + ".zip");
if (!sourceFile.exists()) {
throw new IllegalStateException("The App file does not exists in the 'zipDirectory': " + sourceFile);
} else if (!sourceFile.isFile()) {
throw new IllegalStateException("The App file is not a file: " + sourceFile);
}
return sourceFile;
}
private void uploadApp(Long appId, File appZip) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl 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 (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());
} 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());
} else {
this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appId + ")");
api.getAppDefinitionsJerseyApi().importApp(appId, multipart, true);
}
}
} catch (ParseException pe) {
throw new MojoExecutionException("The app ZIP could not be wrapped in a multipart", pe);
} finally {
bistream.close();
}
}
}

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;
@@ -12,9 +26,18 @@ import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
/**
* 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();
Long appId = this.findAppModel(true).getId();
File appZip = this.downloadApp(appId);
File toAppZip = new File(this.zipDirectory, this.apsAppName + ".zip");
@@ -42,7 +65,7 @@ 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");
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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 javax.ws.rs.core.Response;
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.DocumentTemplateLight;
import com.inteligr8.alfresco.activiti.model.EmailTemplate;
import com.inteligr8.maven.aps.modeling.normalizer.ApsTemplateJsonNormalizer;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* 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().getId() + ".doc-template.docx");
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().getId() + ".doc-template.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().getId() + ".custom-email-template.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() + ".system-email-template.json");
ModelUtil.getInstance().writeJson(sjson, sfile, this.diffFriendly);
if (this.normalize)
new ApsTemplateJsonNormalizer(this.diffFriendly).normalizeFile(sfile, null);
}
}
}
} catch (IOException ie) {
throw new MojoExecutionException("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 {

View File

@@ -0,0 +1,70 @@
/*
* 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;
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.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionPublishRepresentation;
/**
* 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 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.findAppModel(false).getId();
try {
this.publishApp(appId);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be published", ie);
}
}
private void publishApp(Long appId) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
AppDefinitionPublishRepresentation appDefPublish = new AppDefinitionPublishRepresentation();
appDefPublish.setComment(this.comment);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Publishing app: " + appId);
} else {
this.getLog().info("Publishing app: " + appId);
api.getAppDefinitionsApi().publish(appId, appDefPublish);
}
}
}

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;

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 MojoExecutionException("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 {

View File

@@ -0,0 +1,142 @@
/*
* 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.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.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
/**
* 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 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;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
File sourceFile = this.validateSourceDirectory();
ModelRepresentation appModel = this.findAppModel(false);
try {
this.uploadApp(appModel, sourceFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be uploaded", ie);
}
}
protected File validateSourceDirectory() {
if (!this.zipDirectory.exists()) {
throw new IllegalStateException("The 'zipDirectory' does not exist: " + this.zipDirectory);
} else if (!this.zipDirectory.isDirectory()) {
throw new IllegalStateException("The 'zipDirectory' is not a directory: " + this.zipDirectory);
}
File sourceFile = new File(this.zipDirectory, this.apsAppName + ".zip");
if (!sourceFile.exists()) {
throw new IllegalStateException("The App file does not exists in the 'zipDirectory': " + sourceFile);
} else if (!sourceFile.isFile()) {
throw new IllegalStateException("The App file is not a file: " + sourceFile);
}
return sourceFile;
}
private void uploadApp(ModelRepresentation appModel, File appZip) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
FileInputStream fistream = new FileInputStream(appZip);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
FileMultipartJersey multipart = FileMultipartJersey.from(appZip.getName(), bistream);
if (appModel == null) {
if (this.publish) {
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 MojoExecutionException(appDefUpdate.getErrorDescription());
}
} else {
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) {
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 MojoExecutionException(appDefUpdate.getErrorDescription());
}
} else {
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) {
throw new MojoExecutionException("The app ZIP could not be wrapped in a multipart", pe);
} finally {
bistream.close();
}
}
}

View File

@@ -0,0 +1,173 @@
/*
* 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.EmailTemplateLight;
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(".doc-template.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 docxfile = new File(file.getParent(), file.getName().substring(0, file.getName().length() - ".json".length()) + ".docx");
if (!docxfile.exists())
throw new FileNotFoundException("The file, '" + docxfile.getName() + "' was expected and not found");
FileInputStream fistream = new FileInputStream(docxfile);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
FileMultipartJersey multipart = FileMultipartJersey.from(docxfile.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(".custom-email-template.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(".system-email-template.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 MojoExecutionException("The APS templates JSON files could not be parsed", jpe);
} catch (IOException ie) {
throw new MojoExecutionException("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;
}

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;
@@ -9,36 +24,70 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsAppJsonTranslator implements ApsFileTranslator {
/**
* 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);
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsProcessIndex;
private final Index<Long, String> fileProcessIndex;
private Map<String, Group> apsOrgIndex;
private Index<Long, String> apsProcessIndex;
private 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(
Map<String, Group> apsOrgIndex,
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> fileProcessIndex) {
this.apsOrgIndex = apsOrgIndex;
super(api, 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();
@@ -76,40 +125,78 @@ public class ApsAppJsonTranslator implements ApsFileTranslator {
// 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();
long apsOrgId = this.apsOrgIndex.get(fileOrgName).getId();
if (fileOrgId != apsOrgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId);
jsonGroup.put("id", apsOrgId);
switch (identityType.toLowerCase()) {
case "user":
this.logger.warn("Found 'user' identity, which should never be used for portablility; removing ...");
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,36 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.activiti.model.Datum;
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.Group;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
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);
@@ -31,18 +64,34 @@ public class ApsAppTranslator implements ApsAppCrawlable {
private final File appDirectory;
private boolean indexesBuilt = false;
private Map<String, Group> apsOrgIndex;
private Map<String, GroupLight> apsOrgIndex;
private Index<Long, String> apsFormIndex;
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;
@@ -95,8 +144,9 @@ public class ApsAppTranslator implements ApsAppCrawlable {
throw new IllegalStateException("The indexes are never built");
return new ApsAppJsonTranslator(
this.apsOrgIndex,
this.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.fileProcessIndex);
}
@@ -115,6 +165,7 @@ public class ApsAppTranslator implements ApsAppCrawlable {
throw new IllegalStateException("The indexes are never built");
return new ApsProcessJsonTranslator(
this.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex);
@@ -126,6 +177,7 @@ public class ApsAppTranslator implements ApsAppCrawlable {
throw new IllegalStateException("The indexes are never built");
return new ApsProcessBpmnTranslator(
this.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex,
@@ -137,26 +189,24 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return (tenants == null || tenants.isEmpty()) ? null : tenants.iterator().next().getId();
}
protected Map<String, Group> buildApsGroupMap(long tenantId) {
List<Group> groups = this.api.getAdminApi().getGroups(tenantId, true, true);
protected Map<String, GroupLight> buildApsGroupMap(long tenantId) {
List<GroupLight> groups = this.api.getAdminApi().getGroups(tenantId, true, true);
Map<String, Group> map = new HashMap<>(groups.size());
for (Group group : groups)
Map<String, GroupLight> map = new HashMap<>(groups.size());
for (GroupLight group : groups)
map.put(group.getName(), group);
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

@@ -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,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;
@@ -11,11 +25,23 @@ 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 Form JSON configuration file translator.
*
* This will translate the form ID embedded into APS Form descriptor.
*
* @author brian@inteligr8.com
*/
public class ApsFormJsonTranslator implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsFormJsonTranslator.class);
private final Index<Long, String> apsIndex;
/**
* 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;
}

View File

@@ -0,0 +1,58 @@
/*
* 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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) {
this.api = api;
this.apsOrgIndex = apsOrgIndex;
}
protected long createOrganization(String groupName) {
this.logger.info("Creating organization '{}' in APS", groupName);
GroupLight group = this.api.getAdminApi().createGroup(new Group()
.withName(groupName)
.withType(1L)); // an organization, not capability
this.apsOrgIndex.put(groupName, group);
return group.getId();
}
}

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;
@@ -22,30 +36,54 @@ import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsProcessBpmnTranslator implements ApsFileTranslator {
/**
* 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";
private static final String NAMESPACE_ACTIVITI_MODELER = "http://activiti.com/modeler";
private final Logger logger = LoggerFactory.getLogger(ApsProcessBpmnTranslator.class);
private final Index<Long, String> apsIndex;
private final Map<String, Group> apsOrgIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
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, Group> apsOrgIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex,
Index<Long, String> fileFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.fileFormIndex = fileFormIndex;
}
@@ -104,8 +142,6 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator {
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;
}
@@ -141,22 +177,26 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator {
String groupName = groupNameElement.getTextContent().trim();
this.logger.trace("Found '{}' candidate group in the APS Process BPMN model", groupName);
if (this.apsOrgIndex.containsKey(groupName)) {
long apsOrgId = this.apsOrgIndex.get(groupName).getId();
groupIdsList.add(apsOrgId);
if (apsOrgId != orgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing model", groupName, apsOrgId);
groupNameElement = (Element)groupNameElement.getOwnerDocument().renameNode(groupNameElement,
groupNameElement.getNamespaceURI(), "modeler:group-info-name-" + apsOrgId);
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", groupName);
}
GroupLight apsOrg = this.apsOrgIndex.get(groupName);
long apsOrgId = 0L;
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; creating it ...", groupName);
apsOrgId = this.createOrganization(groupName);
} else {
// FIXME automatically add the group?
this.logger.debug("The organization '{}' does not exist in APS; leaving unchanged", groupName);
groupIdsList.add(orgId);
apsOrgId = apsOrg.getId();
}
if (apsOrgId != orgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing model", groupName, apsOrgId);
groupNameElement = (Element)groupNameElement.getOwnerDocument().renameNode(groupNameElement,
groupNameElement.getNamespaceURI(), "modeler:group-info-name-" + apsOrgId);
groupIdsList.add(apsOrgId);
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", groupName);
groupIdsList.add(apsOrgId);
}
}
@@ -334,14 +374,5 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator {
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;
@@ -10,21 +24,45 @@ 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.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsProcessJsonTranslator implements ApsFileTranslator {
/**
* 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, Group> apsOrgIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
/**
* 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.
*/
public ApsProcessJsonTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, Group> apsOrgIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
@@ -39,21 +77,22 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
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.translateForms(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");
@@ -80,7 +119,8 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
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)) {
@@ -107,10 +147,16 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
if (this.apsOrgIndex.containsKey(fileOrgName)) {
long fileOrgId = jsonCandidateGroup.get("id").asLong();
long apsOrgId = this.apsOrgIndex.get(fileOrgName).getId();
if (apsOrgId != fileOrgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId);
jsonCandidateGroup.put("id", apsOrgId);
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);
jsonCandidateGroup.put("id", apsGroupId);
return true;
} else if (apsOrg.getId() != fileOrgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrg.getId());
jsonCandidateGroup.put("id", apsOrg.getId());
return true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName);
@@ -125,7 +171,8 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
private boolean translateForms(ObjectNode jsonDescriptor) {
private boolean translateForms(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating forms in process: {}", processName);
boolean changed = false;
for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) {
@@ -148,18 +195,24 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
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)) {
@@ -188,24 +241,30 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
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,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.ApsPublicRestApi;
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 ApsPublicRestApi api;
private boolean indexesBuilt = false;
private Index<Long, String> apsDocumentTemplateIndex;
private Index<Long, String> apsCustomEmailTemplateIndex;
public ApsTemplateTranslator(ApsPublicRestApi 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;