40 Commits

Author SHA1 Message Date
45320f3802 v1.5.0 pom 2025-04-08 17:07:13 -04:00
e8663123c7 Merge branch 'develop' into stable 2025-04-08 17:07:00 -04:00
85ad7cdded v1.5.x 2025-04-08 17:06:21 -04:00
36aa216a59 fixed jaxb/jakarta runtime issue 2025-04-08 17:05:34 -04:00
ee0f0b4824 added DecisionTable support 2025-04-08 15:25:46 -04:00
d94c0783db v1.4.4 pom 2024-03-13 10:44:55 -04:00
997fe3d286 Merge branch 'develop' into stable 2024-03-13 10:44:38 -04:00
04d26fb251 publish failures result in 200 code; fudging to 412 code 2024-03-13 10:41:21 -04:00
9b4287b811 only set OAuth client secret or password if set 2024-03-13 10:35:01 -04:00
653eccec17 v1.4.3 pom 2024-01-23 09:12:54 -05:00
2e88bd5af4 v1.4.2 pom 2024-01-23 09:07:03 -05:00
0eafc03acd Merge branch 'develop' into stable 2024-01-23 09:06:30 -05:00
8d634670e1 added support for encrypted server passwords 2024-01-23 09:06:17 -05:00
2d89466813 Merge branch 'develop' into stable 2023-11-16 13:40:34 -05:00
a856f9580b changed template naming 2023-11-16 13:39:58 -05:00
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
35 changed files with 1877 additions and 197 deletions

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` | |

26
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>1.3.2</version>
<version>1.5.0</version>
<packaging>maven-plugin</packaging>
<name>A Maven plugin for Alfresco Process Services model portability</name>
@@ -42,26 +42,26 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.version>3.6.3</maven.version>
<maven.version>3.9.9</maven.version>
<jersey.version>2.35</jersey.version>
<jersey.version>3.1.10</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-api</artifactId>
<version>2.0.3-aps1</version>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-client</artifactId>
<version>2.0.2-jersey</version>
<version>3.0.2-jersey</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
@@ -76,7 +76,7 @@
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>file-management</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
@@ -87,7 +87,7 @@
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.6.0</version>
<version>3.15.1</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -111,7 +111,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -121,7 +121,7 @@
<plugins>
<plugin>
<artifactId>maven-invoker-plugin</artifactId>
<version>3.2.2</version>
<version>3.9.0</version>
<configuration>
<projectsDirectory>${basedir}/src/it</projectsDirectory>
<cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
@@ -146,7 +146,7 @@
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.6.1</version>
<version>3.15.1</version>
<configuration>
<goalPrefix>aps-model</goalPrefix>
</configuration>
@@ -168,7 +168,7 @@
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
<version>2.0.0</version>
<version>2.2.0</version>
<executions>
<execution>
<goals>
@@ -269,7 +269,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>

View File

@@ -45,4 +45,9 @@ public interface ApsAppCrawlable {
*/
ApsFileTransformer getFormJsonTransformer();
/**
* @return A file transformer for APS Decision Table JSON files.
*/
ApsFileTransformer getDecisionTableJsonTransformer();
}

View File

@@ -83,6 +83,7 @@ public class ApsAppCrawler {
this.transform(crawlable.getAppJsonTransformer(), this.appDescriptor, this.appName, null);
this.crawlModels("form-models", crawlable.getFormJsonTransformer());
this.crawlModels("decision-table-models", crawlable.getDecisionTableJsonTransformer());
this.crawlModels("bpmn-models", processTransformers);
this.crawlModels("bpmn-subprocess-models", crawlable.getProcessJsonTransformer());
}

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

@@ -14,13 +14,20 @@
*/
package com.inteligr8.maven.aps.modeling.goal;
import java.util.List;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import com.inteligr8.alfresco.activiti.ApsClientJerseyConfiguration;
import com.inteligr8.alfresco.activiti.ApsClientJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsProtectedRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.Tenant;
/**
* This class adds APS addressbility to extending goals.
@@ -55,8 +62,11 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
@Parameter( property = "aps-model.oauth.tokenUrl", required = false )
protected String oauthTokenUrl;
@Component
private SettingsDecrypter decrypter;
private ApsPublicRestApiJerseyImpl api;
private ApsProtectedRestApiJerseyImpl api;
/**
* Retrieves an APS client configuration.
@@ -66,10 +76,11 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
*
* @return An APS client configuration.
*/
public ApsClientJerseyConfiguration getApsClientConfiguration() {
public ApsClientJerseyConfiguration getApsClientConfiguration() throws MojoExecutionException {
this.getLog().debug("Configuring APS to URL: " + this.activitiAppBaseUrl);
ApsClientJerseyConfiguration config = new ApsClientJerseyConfiguration();
config.setBaseUrl(this.activitiAppBaseUrl);
switch (this.activitiAppAuthType.toUpperCase()) {
case "BASIC":
this.getLog().info("Configuring APS with BASIC authentication");
@@ -79,6 +90,9 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
this.getLog().warn("The Maven configuration has no server '" + this.activitiAppAuthBasicServerId + "'; continuing with default credentials");
if (creds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(creds))
.getServer();
this.getLog().debug("Username: " + creds.getUsername());
config.setBasicAuthUsername(creds.getUsername());
config.setBasicAuthPassword(creds.getPassword());
@@ -92,19 +106,29 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
if ((this.oauthClientServerId != null || this.oauthServerId != null) && clientCreds == null && oauthCreds == null)
this.getLog().warn("The Maven configuration has no server '" + this.oauthClientServerId + "' or '" + this.oauthServerId + "'; continuing without credentials");
this.getLog().debug("OAuth Code: " + this.oauthCode);
config.setOAuthAuthCode(this.oauthCode);
if (this.oauthCode != null) {
this.getLog().debug("OAuth Code: " + this.oauthCode);
config.setOAuthAuthCode(this.oauthCode);
}
if (clientCreds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(clientCreds))
.getServer();
this.getLog().debug("OAuth Client ID: " + clientCreds.getUsername());
config.setOAuthClientId(clientCreds.getUsername());
config.setOAuthClientSecret(clientCreds.getPassword());
if (clientCreds.getPassword() != null && clientCreds.getPassword().length() > 0)
config.setOAuthClientSecret(clientCreds.getPassword());
}
if (oauthCreds != null) {
creds = this.decrypter.decrypt(new DefaultSettingsDecryptionRequest(oauthCreds))
.getServer();
this.getLog().debug("OAuth Username: " + oauthCreds.getUsername());
config.setOAuthUsername(oauthCreds.getUsername());
config.setOAuthPassword(oauthCreds.getPassword());
if (oauthCreds.getPassword() != null && oauthCreds.getPassword().length() > 0)
config.setOAuthPassword(oauthCreds.getPassword());
}
config.setOAuthTokenUrl(this.oauthTokenUrl);
@@ -117,16 +141,35 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
}
/**
* This method constructs and caches the APS API accessor.
*
* @return An APS API instance.
* @throws MojoExecutionException The APS API failed to initialize.
*/
public synchronized ApsPublicRestApiJerseyImpl getApsApi() {
public synchronized ApsProtectedRestApiJerseyImpl getApsApi() throws MojoExecutionException {
if (this.api == null) {
ApsClientJerseyConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config);
this.api = new ApsPublicRestApiJerseyImpl(apsClient);
this.api = new ApsProtectedRestApiJerseyImpl(apsClient);
}
return this.api;
}
/**
* This method makes the appropriate service calls to find the first APS
* tenant ID from the configured APS service.
*
* This method does not cache the result.
*
* @return An APS tenant ID; null only if there are no tenants.
* @throws MojoExecutionException The APS API failed to initialize.
*/
protected Long findTenantId() throws MojoExecutionException {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
if (tenants == null || tenants.isEmpty())
return null;
return tenants.iterator().next().getId();
}
}

View File

@@ -15,17 +15,15 @@
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.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
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;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
/**
* This class adds the APS App name to APS addressibility to extending goals.
@@ -36,46 +34,31 @@ import com.inteligr8.alfresco.activiti.model.Tenant;
*
* @author brian@inteligr8.com
*/
public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal {
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 IDs.
* 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 respective IDs; may be empty; never null.
* @return A map of APS App names to their model; may be empty; never null.
* @throws MojoExecutionException The APS API failed to initialize.
*/
protected Map<String, Long> findAppNameIds() {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
protected Map<String, ModelRepresentation> buildAppNameMap() throws MojoExecutionException {
ApsPublicRestApi api = this.getApsApi();
Map<String, Long> apps = new HashMap<>(16);
Map<String, ModelRepresentation> apps = new HashMap<>(16);
this.getLog().debug("Searching for all APS Apps");
ResultListDataRepresentation results = api.getModelsApi().get("everyone", null, ModelType.App.getId(), null);
ResultList<ModelRepresentation> 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());
for (ModelRepresentation model : results.getData()) {
String name = model.getName();
apps.put(name, model);
}
return apps;
@@ -88,11 +71,11 @@ public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal {
* This method does not cache the result.
*
* @param failOnNotFound true to fail if not found; false to return null.
* @return An APS App ID; null if not found.
* @return An APS App model; null if not found.
* @throws MojoExecutionException The APS App could not be found.
*/
protected Long findAppId(boolean failOnNotFound) throws MojoExecutionException {
return this.findAppIdByName(this.apsAppName, failOnNotFound);
protected ModelRepresentation findAppModel(boolean failOnNotFound) throws MojoExecutionException {
return this.findAppModelByName(this.apsAppName, failOnNotFound);
}
/**
@@ -103,15 +86,15 @@ public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal {
*
* @param apsName An APS App name.
* @param failOnNotFound true to fail if not found; false to return null.
* @return An APS App ID; null if not found.
* @return An APS App model; null if not found.
* @throws MojoExecutionException The APS App could not be found.
*/
protected Long findAppIdByName(String appName, boolean failOnNotFound) throws MojoExecutionException {
Map<String, Long> apps = this.findAppNameIds();
Long appId = apps.get(this.apsAppName);
if (failOnNotFound && appId == null)
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 appId;
return appModel;
}
}

View File

@@ -26,13 +26,12 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.api.ModelsApi;
import com.inteligr8.alfresco.activiti.model.Datum;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.PermissionLevel;
import com.inteligr8.alfresco.activiti.model.PermissionLight;
import com.inteligr8.alfresco.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.ShareInfoRequest;
import com.inteligr8.alfresco.activiti.model.SharePermission;
import com.inteligr8.alfresco.activiti.model.Tenant;
@@ -107,15 +106,13 @@ public class ApsShareGoal extends ApsAddressibleGoal {
this.shareModels(ModelsApi.ModelType.Form, this.formReaderSet, this.formEditorSet);
}
private void shareModels(ModelsApi.ModelType modelType, Set<String> readers, Set<String> editors) {
ResultListDataRepresentation models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
private void shareModels(ModelsApi.ModelType modelType, Set<String> readers, Set<String> editors) throws MojoExecutionException {
ResultList<ModelRepresentation> models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
if (models.getData() == null)
return;
for (Datum datum : models.getData()) {
Number modelId = (Number)datum.getAdditionalProperties().get("id");
String modelName = (String)datum.getAdditionalProperties().get("name");
if (this.modelName != null && !this.modelName.equals(modelName))
for (ModelRepresentation model : models.getData()) {
if (this.modelName != null && !this.modelName.equals(model.getName()))
continue;
Set<String> groupsAddressed = new HashSet<>();
@@ -123,7 +120,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
Set<String> editorsUnaddressed = new HashSet<>(editors);
ShareInfoRequest changeRequest = new ShareInfoRequest();
ResultList<SharePermission> shares = this.getApsApi().getShareApi().getShareInfo(modelId.toString());
ResultList<SharePermission> shares = this.getApsApi().getShareApi().getShareInfo(model.getId().toString());
if (shares.getData() != null) {
for (SharePermission share : shares.getData()) {
if (share.getGroup() != null) {
@@ -145,7 +142,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
if (!changeRequest.getAdded().isEmpty() || !changeRequest.getUpdated().isEmpty() || !changeRequest.getRemoved().isEmpty()) {
this.getLog().info("Sharing model: " + modelType + " => '" + modelName + "'");
this.getApsApi().getShareApi().setShareInfo(modelId.toString(), changeRequest);
this.getApsApi().getShareApi().setShareInfo(model.getId().toString(), changeRequest);
}
}
}
@@ -221,7 +218,7 @@ public class ApsShareGoal extends ApsAddressibleGoal {
return params;
}
protected void buildIdentityIndex() {
protected void buildIdentityIndex() throws MojoExecutionException {
List<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
for (Tenant tenant : tenants) {
List<GroupLight> groups = this.getApsApi().getAdminApi().getGroups(tenant.getId(), true, true);

View File

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

View File

@@ -24,7 +24,7 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
/**
* A class that implements an APS service download goal.
@@ -37,7 +37,7 @@ import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
*/
@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;
@@ -46,7 +46,7 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateTargetDirectory();
Long appId = this.findAppId(true);
Long appId = this.findAppModel(true).getId();
File appZip = this.downloadApp(appId);
File toAppZip = new File(this.zipDirectory, this.apsAppName + ".zip");
@@ -56,7 +56,7 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
try {
FileUtils.copyFile(appZip, toAppZip);
} catch (IOException ie) {
throw new MojoExecutionException("The downloaded APS App could not be saved", ie);
throw new MojoFailureException("The downloaded APS App could not be saved", ie);
}
}
@@ -65,12 +65,12 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal {
this.getLog().debug("Creating APS Apps directory: " + this.zipDirectory);
this.zipDirectory.mkdirs();
} else if (!this.zipDirectory.isDirectory()) {
throw new IllegalStateException("The 'targetDirectory' refers to a file and not a directory");
throw new IllegalStateException("The 'zipDirectory' refers to a file and not a directory");
}
}
private File downloadApp(long appId) {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
private File downloadApp(long appId) throws MojoExecutionException {
ApsPublicRestApi api = this.getApsApi();
this.getLog().debug("Downloading APS App: " + appId);
return api.getAppDefinitionsApi().export(appId);
}

View File

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

View File

@@ -63,7 +63,7 @@ public class PackAppGoal extends ApsAppGoal {
try {
this.pack(appDirectory, targetFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be packed", ie);
throw new MojoFailureException("The APS App could not be packed", ie);
}
}

View File

@@ -22,8 +22,12 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.AppDefinitionPublishRepresentation;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
/**
* A class that implements an APS Service publish goal.
@@ -35,28 +39,38 @@ import com.inteligr8.alfresco.activiti.model.AppDefinitionPublishRepresentation;
*/
@Mojo( name = "publish-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class PublishAppGoal extends ApsAppAccessibleGoal {
public class PublishAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.publish.comment", required = true, defaultValue = "Automated by 'aps-model-maven-plugin'" )
protected String comment;
@Parameter( property = "aps-model.dryRun", required = true, defaultValue = "false" )
protected boolean dryRun;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
Long appId = this.findAppId(false);
Long appId = this.findAppModel(false).getId();
try {
this.publishApp(appId);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be published", ie);
throw new MojoFailureException("The APS App could not be published", ie);
}
}
private void publishApp(Long appId) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
ApsPublicRestApi api = this.getApsApi();
AppDefinitionPublishRepresentation appDefPublish = new AppDefinitionPublishRepresentation();
appDefPublish.setComment(this.comment);
api.getAppDefinitionsApi().publish(appId, appDefPublish);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Publishing app: " + appId);
} else {
this.getLog().info("Publishing app: " + appId);
AppDefinitionUpdateResultRepresentation response = api.getAppDefinitionsApi().publish(appId, appDefPublish);
if (Boolean.TRUE.equals(response.getError()))
throw new WebApplicationException(response.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
}
}

View File

@@ -33,7 +33,7 @@ import com.inteligr8.maven.aps.modeling.translator.ApsAppTranslator;
* 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 mdoel elements are used to remap IDs between environments.
* 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
@@ -43,7 +43,7 @@ import com.inteligr8.maven.aps.modeling.translator.ApsAppTranslator;
*/
@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;
@@ -74,7 +74,7 @@ public class TranslateAppGoal extends ApsAppAccessibleGoal {
ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, apsAppDirectory, true);
crawler.execute(translator);
} catch (IOException ie) {
throw new MojoExecutionException("An I/O issue occurred", ie);
throw new MojoFailureException("An I/O issue occurred", ie);
} catch (IllegalArgumentException iae) {
throw new MojoExecutionException("The input is not supported", iae);
} catch (IllegalStateException ise) {

View File

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

View File

@@ -90,7 +90,7 @@ public class UnpackAppGoal extends ApsAppGoal {
try {
this.unpack(sourceFile, appDirectory);
} catch (IOException ie) {
throw new MojoExecutionException("The downloaded APS App could not be unpacked", ie);
throw new MojoFailureException("The downloaded APS App could not be unpacked", ie);
}
if (this.reformat)
@@ -101,7 +101,7 @@ public class UnpackAppGoal extends ApsAppGoal {
ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, appDirectory, true);
crawler.execute(normalizer);
} catch (IOException ie) {
throw new MojoExecutionException("An I/O issue occurred", ie);
throw new MojoFailureException("An I/O issue occurred", ie);
}
}
}
@@ -223,7 +223,7 @@ public class UnpackAppGoal extends ApsAppGoal {
file.delete();
}
} catch (TransformerException | SAXException | IOException e) {
throw new MojoFailureException("The following file faild to be reformatted: " + file, e);
throw new MojoFailureException("The following file failed to be reformatted: " + file, e);
}
}
}

View File

@@ -26,9 +26,13 @@ import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.ApsPublicRestJerseyApi;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
/**
* A class that implements an APS service upload goal.
@@ -42,13 +46,16 @@ import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
*/
@Mojo( name = "upload-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class UploadAppGoal extends ApsAppAccessibleGoal {
public class UploadAppGoal extends ApsAppAddressibleGoal {
@Parameter( property = "aps-model.upload.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File zipDirectory;
@Parameter( property = "publish", required = true, defaultValue = "false" )
protected boolean publish = false;
@Parameter( property = "aps-model.dryRun", required = true, defaultValue = "false" )
protected boolean dryRun;
protected final int bufferSize = 128 * 1024;
@@ -56,12 +63,12 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
File sourceFile = this.validateSourceDirectory();
Long appId = this.findAppId(false);
ModelRepresentation appModel = this.findAppModel(false);
try {
this.uploadApp(appId, sourceFile);
this.uploadApp(appModel, sourceFile);
} catch (IOException ie) {
throw new MojoExecutionException("The APS App could not be uploaded", ie);
throw new MojoFailureException("The APS App could not be uploaded", ie);
}
}
@@ -83,33 +90,49 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
return sourceFile;
}
private void uploadApp(Long appId, File appZip) throws IOException, MojoExecutionException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
private void uploadApp(ModelRepresentation appModel, File appZip) throws IOException, MojoExecutionException, MojoFailureException {
ApsPublicRestJerseyApi api = this.getApsApi();
FileInputStream fistream = new FileInputStream(appZip);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
FileMultipartJersey multipart = FileMultipartJersey.from(appZip.getName(), bistream);
if (appId == null) {
if (appModel == null) {
if (this.publish) {
this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName);
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new MojoExecutionException(appDefUpdate.getErrorDescription());
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading & publishing new APS App: " + this.apsAppName);
} else {
this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName);
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new WebApplicationException(appDefUpdate.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
} else {
this.getLog().info("Uploading new APS App: " + this.apsAppName);
api.getAppDefinitionsJerseyApi().importApp(multipart, true);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading new APS App: " + this.apsAppName);
} else {
this.getLog().info("Uploading new APS App: " + this.apsAppName);
api.getAppDefinitionsJerseyApi().importApp(multipart, true);
}
}
} else {
if (this.publish) {
this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appId + ")");
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appId, multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new MojoExecutionException(appDefUpdate.getErrorDescription());
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
} else {
this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appModel.getId(), multipart);
if (Boolean.TRUE.equals(appDefUpdate.getError()))
throw new WebApplicationException(appDefUpdate.getErrorDescription(), Response.Status.PRECONDITION_FAILED);
}
} else {
this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appId + ")");
api.getAppDefinitionsJerseyApi().importApp(appId, multipart, true);
if (this.dryRun) {
this.getLog().info("[DRYRUN]: Uploading & versioning APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
} else {
this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appModel.getId() + ")");
api.getAppDefinitionsJerseyApi().importApp(appModel.getId(), multipart, true);
}
}
}
} catch (ParseException pe) {

View File

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

View File

@@ -62,6 +62,11 @@ public class ApsAppNormalizer implements ApsAppCrawlable {
public ApsFileTransformer getFormJsonTransformer() {
return new ApsFormJsonNormalizer();
}
@Override
public ApsFileTransformer getDecisionTableJsonTransformer() {
return new ApsDecisionTableJsonNormalizer();
}
@Override
public ApsFileTransformer getProcessJsonTransformer() {

View File

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

View File

@@ -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

@@ -26,11 +26,11 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.Datum;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.Tenant;
import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawlable;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
@@ -64,6 +64,7 @@ public class ApsAppTranslator implements ApsAppCrawlable {
private boolean indexesBuilt = false;
private Map<String, GroupLight> apsOrgIndex;
private Index<Long, String> apsFormIndex;
private Index<Long, String> apsDecisionTableIndex;
private Index<Long, String> apsProcessIndex;
private Index<Long, String> fileFormIndex;
private Index<Long, String> fileProcessIndex;
@@ -107,6 +108,9 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.apsFormIndex = this.buildApsModelIndex(ModelType.Form);
this.logLarge("APS form models: {}", this.apsFormIndex);
this.apsDecisionTableIndex = this.buildApsModelIndex(ModelType.DecisionTable);
this.logLarge("APS decision table models: {}", this.apsDecisionTableIndex);
this.fileFormIndex = this.buildFileIndex("form-models", this.apsFormIndex, true);
this.logLarge("File form models: {}", this.fileFormIndex);
@@ -156,6 +160,15 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return new ApsFormJsonTranslator(
this.apsFormIndex);
}
@Override
public ApsFileTransformer getDecisionTableJsonTransformer() {
if (!this.indexesBuilt)
throw new IllegalStateException("The indexes are never built");
return new ApsDecisionTableJsonTranslator(
this.apsDecisionTableIndex);
}
@Override
public ApsFileTransformer getProcessJsonTransformer() {
@@ -166,7 +179,8 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex);
this.apsFormIndex,
this.apsDecisionTableIndex);
}
@Override
@@ -179,6 +193,7 @@ public class ApsAppTranslator implements ApsAppCrawlable {
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex,
this.apsDecisionTableIndex,
this.fileFormIndex);
}
@@ -196,17 +211,15 @@ public class ApsAppTranslator implements ApsAppCrawlable {
return map;
}
protected Index<Long, String> buildApsModelIndex(ModelType modelType) {
ResultListDataRepresentation results = this.api.getModelsApi().get("everyone", null, modelType.getId(), null);
protected Index<Long, String> buildApsModelIndex(ModelType modelType) {
ResultList<ModelRepresentation> results = this.api.getModelsApi().get("everyone", null, modelType.getId(), null);
this.logger.debug("APS {} models found: {} out of {}", modelType, results.getSize(), results.getTotal());
Index<Long, String> map = new Index<>(results.getSize().intValue(), false);
try {
for (Datum datum : results.getData()) {
Number defId = (Number)datum.getAdditionalProperties().get("id");
String defName = (String)datum.getAdditionalProperties().get("name");
map.put(defId.longValue(), defName);
for (ModelRepresentation model : results.getData()) {
map.put(model.getId(), model.getName());
// FIXME add paging support
}

View File

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

View File

@@ -14,16 +14,7 @@
*/
package com.inteligr8.maven.aps.modeling.translator;
import java.io.File;
import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* This class implements an APS Form JSON configuration file translator.
@@ -32,10 +23,7 @@ import com.inteligr8.maven.aps.modeling.util.ModelUtil;
*
* @author brian@inteligr8.com
*/
public class ApsFormJsonTranslator implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsFormJsonTranslator.class);
private final Index<Long, String> apsIndex;
public class ApsFormJsonTranslator extends ApsResourceJsonTranslator {
/**
* This constructor initializes the default translator.
@@ -43,36 +31,7 @@ public class ApsFormJsonTranslator implements ApsFileTranslator {
* @param apsFormIndex A map of form IDs to form names as defined in the APS Service.
*/
public ApsFormJsonTranslator(Index<Long, String> apsFormIndex) {
this.apsIndex = apsFormIndex;
}
@Override
public void translateFile(File file, String formName, Long formId) throws IOException {
this.logger.debug("Translating JSON file: {}", file);
boolean changed = false;
ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class);
JsonNode jsonResourceId = jsonDescriptor.get("resourceId");
if (jsonResourceId == null || jsonResourceId.isNull()) {
this.logger.debug("The form has no ID to translate");
return;
}
this.logger.trace("Found ID {} in the '{}' APS Form descriptor", jsonResourceId, formName);
Long apsFormId = this.apsIndex.getFirstKey(formName);
if (apsFormId == null) {
this.logger.debug("The form '{}' does not exist in APS; leaving unchanged", formName);
} else if (!formId.equals(apsFormId)) {
this.logger.debug("The form '{}' exists in APS with ID {}; changing descriptor", formName, apsFormId);
jsonDescriptor.put("resourceId", apsFormId);
changed = true;
} else {
this.logger.trace("The form '{}' ID does not change; leaving unchanged", formName, apsFormId);
}
if (changed)
ModelUtil.getInstance().writeJson(jsonDescriptor, file, false);
super(apsFormIndex);
}
}

View File

@@ -63,7 +63,8 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
private final Index<Long, String> apsIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
private final Index<Long, String> fileFormIndex;
private final Index<Long, String> apsDecisionTableIndex;
private final Index<Long, String> fileFormIndex;
/**
* This constructor initializes the default translator.
@@ -79,12 +80,14 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex,
Index<Long, String> apsDecisionTableIndex,
Index<Long, String> fileFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.fileFormIndex = fileFormIndex;
this.apsDecisionTableIndex = apsDecisionTableIndex;
this.fileFormIndex = fileFormIndex;
}
@Override
@@ -111,6 +114,12 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
Element formKeyElement = (Element)formKeyElements.item(n);
changed = this.translateTaskForm(formKeyElement) || changed;
}
NodeList decisionTableRefElements = ModelUtil.getInstance().xpath(definitionsElement, "//modeler:decisiontable-reference");
for (int n = 0; n < decisionTableRefElements.getLength(); n++) {
Element decisionTableRefElement = (Element)decisionTableRefElements.item(n);
changed = this.translateDecisionTableRef(decisionTableRefElement, bpmn) || changed;
}
NodeList subprocessElements = ModelUtil.getInstance().xpath(definitionsElement, "//tns:subProcess");
for (int n = 0; n < subprocessElements.getLength(); n++) {
@@ -258,6 +267,47 @@ public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements
return changed;
}
private boolean translateDecisionTableRef(Element decisionTableRefElement, Document xmldoc) throws XPathExpressionException {
String dtRefIdStr = decisionTableRefElement.getAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferenceid");
this.logger.trace("Translating decision table: {}", dtRefIdStr);
if (dtRefIdStr == null)
throw new IllegalStateException("A decision table was detected, but no identifier was found");
Long dtRefId = new Long(dtRefIdStr);
boolean changed = false;
String dtRefName = decisionTableRefElement.getAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferencename");
if (dtRefName == null)
throw new IllegalStateException("A decision table was detected, but no name was found: " + dtRefId);
dtRefName = dtRefName.trim();
this.logger.trace("Found '{}' decision table in the APS Process BPMN model", dtRefName);
Long apsDecisionTableId = this.apsDecisionTableIndex.getFirstKey(dtRefName);
if (apsDecisionTableId == null) {
this.logger.debug("The decision table '{}' does not exist in APS; leaving unchanged", dtRefName);
return false;
}
this.logger.trace("Found ID {} for the decision table '{}' in the APS Process BPMN model", apsDecisionTableId, dtRefName);
String dtRefKey = (String) ModelUtil.getInstance().xpath(
decisionTableRefElement.getParentNode(),
"activiti:field[@name=\"decisionTableReferenceKey\"]/activiti:string/text()",
XPathConstants.STRING);
if (dtRefKey == null)
throw new IllegalStateException("A decision table was detected, but no key was found: " + dtRefId);
if (!apsDecisionTableId.equals(dtRefId)) {
this.logger.debug("The decision table '{}' exists in APS with ID {}; changing model", dtRefName, apsDecisionTableId);
decisionTableRefElement.setAttributeNS(NAMESPACE_ACTIVITI_MODELER, "decisiontablereferenceid", apsDecisionTableId.toString());
changed = true;
} else {
this.logger.trace("The decision table '{}' key does not change; leaving unchanged", dtRefName);
}
return changed;
}
private boolean translateSubprocess(Element subprocessElement) throws XPathExpressionException {
this.logger.trace("Translating subprocess: {}", subprocessElement.getAttribute("id"));

View File

@@ -48,6 +48,7 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
private final Index<Long, String> apsIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
private final Index<Long, String> apsDecisionTableIndex;
/**
* This constructor initializes the default translator.
@@ -56,16 +57,19 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
* @param apsProcessIndex A map of process IDs to process names as defined in the APS Service.
* @param apsOrgIndex A map of organizations (groups) to organization meta-data as defined in the APS Service.
* @param apsFormIndex A map of form IDs to form names as defined in the APS Service.
* @param apsDecisionTableIndex A map of decision table IDs to decision table names as defined in the APS Service.
*/
public ApsProcessJsonTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex) {
Index<Long, String> apsFormIndex,
Index<Long, String> apsDecisionTableIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.apsDecisionTableIndex = apsDecisionTableIndex;
}
@Override
@@ -79,7 +83,7 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
changed = this.translateResourceId(jsonDescriptor, processName, apsProcessId) || changed;
changed = this.translateOrganizations(jsonDescriptor, processName) || changed;
changed = this.translateForms(jsonDescriptor, processName) || changed;
changed = this.translateResources(jsonDescriptor, processName) || changed;
changed = this.translateSubprocesses(jsonDescriptor, processName) || changed;
if (changed)
@@ -171,8 +175,8 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
private boolean translateForms(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating forms in process: {}", processName);
private boolean translateResources(ObjectNode jsonDescriptor, String processName) {
this.logger.trace("Translating resources in process: {}", processName);
boolean changed = false;
for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) {
@@ -182,6 +186,9 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
JsonNode jsonFormRef = (JsonNode)jsonChildShape.get("properties").get("formreference");
changed = this.translateReference(jsonFormRef, this.apsFormIndex) || changed;
JsonNode jsonDecitionTableRef = (JsonNode)jsonChildShape.get("properties").get("decisiontaskdecisiontablereference");
changed = this.translateReference(jsonDecitionTableRef, this.apsDecisionTableIndex) || changed;
} catch (NullPointerException npe) {
// suppress; gracefully skip
}
@@ -195,13 +202,18 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
return false;
this.logger.trace("Found a conditional sequence flow in the APS Process descriptor");
ObjectNode expression = (ObjectNode)jsonConditionalSequenceFlow.get("expression");
boolean changed = false;
changed = this.translateNamedId(expression, this.apsFormIndex, "outcomeForm") || changed;
changed = this.translateNamedId(expression, this.apsFormIndex, "rightOutcomeForm") || changed;
return changed;
boolean changed = false;
ObjectNode expression = (ObjectNode)jsonConditionalSequenceFlow.get("expression");
while (expression != null) {
changed = this.translateNamedId(expression, this.apsFormIndex, "outcomeForm") || changed;
changed = this.translateNamedId(expression, this.apsFormIndex, "rightOutcomeForm") || changed;
expression = (ObjectNode)expression.get("nextCondition");
}
return changed;
}
@@ -236,24 +248,30 @@ public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements
String idField = prefix == null ? "id" : prefix + "Id";
ObjectNode jsonRef = (ObjectNode)_jsonRef;
String modelName = jsonRef.get(nameField).asText();
this.logger.trace("Found model '{}' in the APS Process descriptor", modelName);
if (apsIndex.containsValue(modelName)) {
long modelId = jsonRef.get(idField).asLong();
long apsModelId = apsIndex.getFirstKey(modelName);
if (apsModelId != modelId) {
this.logger.debug("The model '{}' exists in APS with ID {}; leaving unchanged", modelName, apsModelId);
jsonRef.put(idField, apsModelId);
return true;
} else {
this.logger.trace("The model '{}' ID does not change; leaving unchanged", modelName);
}
} else {
this.logger.debug("The model '{}' does not exists in APS; leaving unchanged", modelName);
JsonNode jsonName = jsonRef.get(nameField);
if (jsonName == null) {
this.logger.trace("The expression does not have an APS model name field ({})", nameField);
return false;
}
return false;
String modelName = jsonName.asText();
this.logger.trace("Found model '{}' in the APS Process descriptor", modelName);
if (!apsIndex.containsValue(modelName)) {
this.logger.debug("The model '{}' does not exists in APS; leaving unchanged", modelName);
return false;
}
JsonNode jsonId = jsonRef.get(idField);
long modelId = jsonId == null ? -1L : jsonId.asLong();
long apsModelId = apsIndex.getFirstKey(modelName);
if (apsModelId != modelId) {
this.logger.debug("The model '{}' exists in APS with ID {}", modelName, apsModelId);
jsonRef.put(idField, apsModelId);
return true;
} else {
this.logger.trace("The model '{}' ID does not change; leaving unchanged", modelName);
return false;
}
}

View File

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

View File

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

View File

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

View File

@@ -48,10 +48,11 @@ public class ArrayNodeObjectSorter {
* 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.
* @see com.inteligr8.maven.aps.modeling.util.ObjectNodeComparator
*/
public boolean sort(ArrayNode arrayNode, String jsonObjectFieldName) {
return this.sort(arrayNode, new ObjectNodeComparator(jsonObjectFieldName));

View File

@@ -35,6 +35,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* `allowDuplicateValues` to `false`.
*
* @author brian@inteligr8.com
* @param <K> An index key type.
* @param <V> An index value type.
*/
public class Index<K, V> {

View File

@@ -28,6 +28,8 @@ import java.util.Map;
* 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>> {

View File

@@ -52,6 +52,8 @@ 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;
/**
@@ -73,8 +75,8 @@ public class ModelUtil {
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();
@@ -296,6 +298,27 @@ public class ModelUtil {
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.
@@ -384,6 +407,19 @@ 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);
}