24 Commits

Author SHA1 Message Date
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
24 changed files with 1225 additions and 100 deletions

View File

@@ -6,7 +6,7 @@
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>1.3.5</version>
<version>1.4.4</version>
<packaging>maven-plugin</packaging>
<name>A Maven plugin for Alfresco Process Services model portability</name>
@@ -51,12 +51,12 @@
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-api</artifactId>
<version>2.0.8</version>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-client</artifactId>
<version>2.0.3-jersey</version>
<version>2.0.5-jersey</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

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.model.Tenant;
/**
* This class adds APS addressbility to extending goals.
@@ -55,6 +62,9 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
@Parameter( property = "aps-model.oauth.tokenUrl", required = false )
protected String oauthTokenUrl;
@Component
private SettingsDecrypter decrypter;
private ApsPublicRestApiJerseyImpl api;
@@ -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,9 +141,12 @@ 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 ApsPublicRestApiJerseyImpl getApsApi() throws MojoExecutionException {
if (this.api == null) {
ApsClientJerseyConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config);
@@ -128,5 +155,21 @@ public abstract class ApsAddressibleGoal extends DisablableGoal {
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.activiti.model.Datum;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType;
import com.inteligr8.alfresco.activiti.model.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() {
protected Map<String, ModelRepresentation> buildAppNameMap() throws MojoExecutionException {
ApsPublicRestApiJerseyImpl 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<Datum> 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.Datum;
import com.inteligr8.activiti.model.ResultList;
import com.inteligr8.alfresco.activiti.api.ModelsApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
import com.inteligr8.alfresco.activiti.model.PermissionLevel;
import com.inteligr8.alfresco.activiti.model.PermissionLight;
import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
import com.inteligr8.alfresco.activiti.model.ShareInfoRequest;
import com.inteligr8.alfresco.activiti.model.SharePermission;
import com.inteligr8.alfresco.activiti.model.Tenant;
@@ -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<Datum> 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

@@ -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,11 +65,11 @@ 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) {
private File downloadApp(long appId) throws MojoExecutionException {
ApsPublicRestApiJerseyImpl 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 javax.ws.rs.core.Response;
import org.apache.commons.io.FileUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.alfresco.activiti.model.BaseTemplateLight;
import com.inteligr8.alfresco.activiti.model.EmailTemplate;
import com.inteligr8.maven.aps.modeling.normalizer.ApsTemplateJsonNormalizer;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
/**
* A class that implements an APS service download goal.
*
* This goal will simply download the APS templates with the specified names
* from the specified APS service.
*
* @author brian@inteligr8.com
*/
@Mojo( name = "download-template", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class DownloadTemplateGoal extends ApsTemplateAddressibleGoal {
@Parameter( property = "aps-model.download.directory", required = true, defaultValue = "${project.build.directory}/aps" )
protected File templateDirectory;
@Parameter( property = "aps-model.normalize" )
protected boolean normalize;
@Parameter( property = "aps-model.normalize.diffFriendly" )
protected boolean diffFriendly;
@Override
public void executeEnabled() throws MojoExecutionException, MojoFailureException {
this.validateTargetDirectory();
Long tenantId = this.findTenantId();
try {
Map<TemplateType, Map<String, ? extends BaseTemplateLight>> templates = this.findTemplates(tenantId);
for (TemplateType ttype : templates.keySet()) {
this.getLog().info("Downloading " + templates.get(ttype).size() + " " + ttype + " templates");
for (Entry<String, ? extends BaseTemplateLight> template : templates.get(ttype).entrySet()) {
switch (ttype) {
case Document:
File dfilebin = new File(this.templateDirectory, template.getValue().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

@@ -16,6 +16,9 @@ package com.inteligr8.maven.aps.modeling.goal;
import java.io.IOException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
@@ -24,6 +27,7 @@ import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionPublishRepresentation;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
/**
* A class that implements an APS Service publish goal.
@@ -35,19 +39,22 @@ 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);
}
}
@@ -56,7 +63,14 @@ public class PublishAppGoal extends ApsAppAccessibleGoal {
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

@@ -20,6 +20,9 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.text.ParseException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
@@ -29,6 +32,7 @@ import org.codehaus.plexus.component.annotations.Component;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
import com.inteligr8.alfresco.activiti.model.ModelRepresentation;
/**
* A class that implements an APS service upload goal.
@@ -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,7 +90,7 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
return sourceFile;
}
private void uploadApp(Long appId, File appZip) throws IOException, MojoExecutionException {
private void uploadApp(ModelRepresentation appModel, File appZip) throws IOException, MojoExecutionException, MojoFailureException {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
FileInputStream fistream = new FileInputStream(appZip);
@@ -91,25 +98,41 @@ public class UploadAppGoal extends ApsAppAccessibleGoal {
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

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

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

View File

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

View File

@@ -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);
}