diff --git a/pom.xml b/pom.xml index 2afd360..acca7e2 100644 --- a/pom.xml +++ b/pom.xml @@ -41,7 +41,7 @@ com.inteligr8.alfresco aps-public-rest-api - 1.2.0 + 1.2.1 aps1 @@ -184,6 +184,7 @@ jar public + true @@ -212,6 +213,12 @@ run + + ${aps-model.baseUrl} + ${aps-model.authType} + ${aps-model.basicAuth.mavenServerId} + ${aps-model.appName} + ${run-it}/pom.xml @@ -223,6 +230,14 @@ + + + + inteligr8-releases + Inteligr8 Releases + https://repos.inteligr8.com/nexus/repository/inteligr8-public + + diff --git a/src/it/deploy-app/pom.xml b/src/it/deploy-app/pom.xml index d995edd..8f24f58 100644 --- a/src/it/deploy-app/pom.xml +++ b/src/it/deploy-app/pom.xml @@ -12,10 +12,6 @@ Deploy App Plugin Tests - - FORMS Core - - @@ -24,14 +20,46 @@ @pom.version@ - download-app - validate + download-unpack-app + generate-sources download-app + unpack-app + + + true + true + + + + translate-app + compile + + translate-app + + + ${project.build.directory}/aps-dev + + + + pack-app + package + + pack-app + + + ${project.build.directory}/aps-dev + ${project.build.directory}/aps-test + + + + deploy-app + package + deploy-app - ${aps.app} + ${project.build.directory}/aps-test diff --git a/src/it/download-app/pom.xml b/src/it/download-app/pom.xml index 6f4c9dd..b191c07 100644 --- a/src/it/download-app/pom.xml +++ b/src/it/download-app/pom.xml @@ -12,10 +12,6 @@ Download App Plugin Tests - - FORMS Core - - @@ -25,23 +21,19 @@ download-app - validate + generate-sources download-app - - ${aps.app} - download-app-other - validate + generate-sources download-app - ${aps.app} - ${basedir}/src/main/app + ${basedir}/src/main/aps @@ -57,8 +49,8 @@ - ${project.build.directory}/app/${aps.app}.zip - ${basedir}/src/main/app/${aps.app}.zip + ${project.build.directory}/aps/${aps-model.appName}.zip + ${basedir}/src/main/aps/${aps-model.appName}.zip diff --git a/src/it/pack-app/pom.xml b/src/it/pack-app/pom.xml index 5d2a52b..5d26679 100644 --- a/src/it/pack-app/pom.xml +++ b/src/it/pack-app/pom.xml @@ -12,10 +12,6 @@ Pack App Plugin Tests - - FORMS Core - - @@ -25,24 +21,23 @@ download-unpack-app - validate + generate-sources download-app unpack-app - ${aps.app} + true pack-app - validate + package pack-app - ${aps.app} - ${basedir}/src/main/app + ${basedir} @@ -58,7 +53,7 @@ - ${basedir}/src/main/app/${aps.app}.zip + ${basedir}/${aps-model.appName}.zip diff --git a/src/it/translate-app/pom.xml b/src/it/translate-app/pom.xml index 0d70c2a..5c38c84 100644 --- a/src/it/translate-app/pom.xml +++ b/src/it/translate-app/pom.xml @@ -12,10 +12,6 @@ Translate App Plugin Tests - - FORMS Core - - @@ -25,24 +21,24 @@ download-unpack-app - validate + generate-sources download-app unpack-app - ${aps.app} + true + true translate-app - validate + compile translate-app - ${aps.app} - ${basedir}/src/main/app + ${basedir}/src/main/aps/app @@ -58,7 +54,7 @@ - ${basedir}/src/main/app/${aps.app}/${aps.app}.json + ${basedir}/src/main/aps/app/${aps-model.appName}/${aps-model.appName}.json diff --git a/src/it/unpack-app/pom.xml b/src/it/unpack-app/pom.xml index 085b737..7dcc93a 100644 --- a/src/it/unpack-app/pom.xml +++ b/src/it/unpack-app/pom.xml @@ -12,10 +12,6 @@ Unpack App Plugin Tests - - FORMS Core - - @@ -25,24 +21,20 @@ download-unpack-app - validate + generate-sources download-app unpack-app - - ${aps.app} - unpack-app - validate + process-sources unpack-app - ${aps.app} - ${basedir}/src/main/app + ${basedir}/src/main/aps/app @@ -58,8 +50,8 @@ - ${project.build.directory}/app/${aps.app}/${aps.app}.json - ${basedir}/src/main/app/${aps.app}/${aps.app}.json + ${project.build.directory}/aps/app/${aps-model.appName}/${aps-model.appName}.json + ${basedir}/src/main/aps/app/${aps-model.appName}/${aps-model.appName}.json diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAddressibleGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/ApsAddressibleGoal.java deleted file mode 100644 index 5da59ff..0000000 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAddressibleGoal.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.inteligr8.maven.aps.modeling; - -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugins.annotations.Parameter; - -import com.inteligr8.alfresco.activiti.ApsClientConfiguration; -import com.inteligr8.alfresco.activiti.ApsClientJerseyImpl; -import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; - -public abstract class ApsAddressibleGoal extends AbstractMojo { - - @Parameter( property = "activitiAppBaseUrl", required = true, defaultValue = "http://localhost:8080/activiti-app" ) - protected String activitiAppBaseUrl; - - @Parameter( property = "activitiAppAuth", required = true, defaultValue = "BASIC" ) - protected String activitiAppAuthType; - - @Parameter( property = "activitiAppBasicUsername", required = false, defaultValue = "admin" ) - protected String activitiAppAuthBasicUsername; - - @Parameter( property = "activitiAppBasicPassword", required = false, defaultValue = "admin" ) - protected String activitiAppAuthBasicPassword; - - @Parameter( property = "oAuthCode", required = false ) - protected String oauthCode; - - @Parameter( property = "oAuthClientId", required = false ) - protected String oauthClientId; - - @Parameter( property = "oAuthClientSecret", required = false ) - protected String oauthClientSecret; - - @Parameter( property = "oAuthUsername", required = false ) - protected String oauthUsername; - - @Parameter( property = "oAuthPassword", required = false ) - protected String oauthPassword; - - @Parameter( property = "oAuthTokenUrl", required = false ) - protected String oauthTokenUrl; - - private ApsPublicRestApiJerseyImpl api; - - public ApsClientConfiguration getApsClientConfiguration() { - ApsClientConfiguration config = new ApsClientConfiguration(); - config.setBaseUrl(this.activitiAppBaseUrl); - switch (this.activitiAppAuthType.toUpperCase()) { - case "BASIC": - this.getLog().info("Configuring APS with BASIC authentication"); - this.getLog().debug("Username: " + this.activitiAppAuthBasicUsername); - config.setBasicAuthUsername(this.activitiAppAuthBasicUsername); - config.setBasicAuthPassword(this.activitiAppAuthBasicPassword); - break; - case "OAUTH": - this.getLog().info("Configuring APS with OAuth authentication"); - this.getLog().debug("OAuth Code: " + this.oauthCode); - this.getLog().debug("OAuth Client ID: " + this.oauthClientId); - this.getLog().debug("OAuth Username: " + this.oauthUsername); - config.setOAuthAuthCode(this.oauthCode); - config.setOAuthTokenUrl(this.oauthTokenUrl); - config.setOAuthClientId(this.oauthClientId); - config.setOAuthClientSecret(this.oauthClientSecret); - config.setOAuthUsername(this.oauthUsername); - config.setOAuthPassword(this.oauthPassword); - break; - default: - throw new IllegalArgumentException("The 'activitiAppAuth' configuration must be either 'Basic' or 'OAuth'"); - } - - return config; - } - - public synchronized ApsPublicRestApiJerseyImpl getApsApi() { - if (this.api == null) { - ApsClientConfiguration config = this.getApsClientConfiguration(); - ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config); - this.api = new ApsPublicRestApiJerseyImpl(apsClient); - } - - return this.api; - } - -} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppAccessibleGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppAccessibleGoal.java deleted file mode 100644 index d59ff31..0000000 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppAccessibleGoal.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.inteligr8.maven.aps.modeling; - -import java.util.List; - -import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; -import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType; -import com.inteligr8.alfresco.activiti.model.Datum; -import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation; -import com.inteligr8.alfresco.activiti.model.Tenant; - -public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal { - - protected Long findTenantId() { - List tenants = this.getApsApi().getAdminApi().getTenants(); - if (tenants == null || tenants.isEmpty()) - return null; - return tenants.iterator().next().getId(); - } - - protected Long findAppByName(String appName) { - ApsPublicRestApiJerseyImpl api = this.getApsApi(); - - this.getLog().debug("Searching for APS App with name: " + appName); - ResultListDataRepresentation results = api.getModelsApi().get(null, 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"); - if (name.equals(appName)) { - Number id = (Number)datum.getAdditionalProperties().get("id"); - this.getLog().info("Found APS App ID: " + id); - return id == null ? null : id.longValue(); - } - } - - return null; - } - -} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsFileTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/ApsFileTranslator.java deleted file mode 100644 index 80d4862..0000000 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsFileTranslator.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.inteligr8.maven.aps.modeling; - -import java.io.File; -import java.io.IOException; - -public interface ApsFileTranslator { - - void translateFile(File file, String modelName, Long modelId) throws IOException; - -} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/CDATAFlexSection.java b/src/main/java/com/inteligr8/maven/aps/modeling/CDATAFlexSection.java deleted file mode 100644 index 195b7ba..0000000 --- a/src/main/java/com/inteligr8/maven/aps/modeling/CDATAFlexSection.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.inteligr8.maven.aps.modeling; - -import org.w3c.dom.CDATASection; -import org.w3c.dom.Node; - -public class CDATAFlexSection { - - private final Node node; - - public CDATAFlexSection(Node node) { - this.node = node; - } - - public String getValue() { - if (this.node instanceof CDATASection) { - return ((CDATASection)this.node).getData(); - } else { - String value = this.node.getTextContent(); - return value == null ? null : this.node.getTextContent().trim(); - } - } - - public void setValue(String value) { - if (this.node instanceof CDATASection) { - ((CDATASection)this.node).setData(value); - } else { - this.node.setTextContent(value); - } - } - -} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/TranslateAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/TranslateAppGoal.java deleted file mode 100644 index faa79ba..0000000 --- a/src/main/java/com/inteligr8/maven/aps/modeling/TranslateAppGoal.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.inteligr8.maven.aps.modeling; - -import java.io.File; -import java.io.IOException; - -import org.apache.commons.io.FileUtils; -import org.apache.maven.execution.MavenSession; -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.apache.maven.project.MavenProject; -import org.codehaus.plexus.component.annotations.Component; - -@Mojo( name = "translate-app", threadSafe = true ) -@Component( role = org.apache.maven.plugin.Mojo.class ) -public class TranslateAppGoal extends ApsAppAccessibleGoal { - - @Parameter( defaultValue = "${project}", readonly = true ) - protected MavenProject project; - - @Parameter( defaultValue = "${session}", readonly = true ) - protected MavenSession session; - - @Parameter( property = "apsAppName", required = true ) - protected String apsAppName; - - @Parameter( property = "sourceDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File sourceDirectory; - - @Parameter( property = "targetDirectory", required = false, defaultValue = "${project.build.directory}/app" ) - protected File targetDirectory; - - @Parameter( property = "overwrite", required = true, defaultValue = "true" ) - protected boolean overwrite = true; - - @Parameter( property = "charset", required = true, defaultValue = "utf-8" ) - protected String charsetName = "utf-8"; - - @Parameter( property = "skip", required = true, defaultValue = "false" ) - protected boolean skip = false; - - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - File apsAppDirectory = this.validateApsAppDirectory(); - File newApsAppDirectory = this.validateTargetDirectory(); - - try { - if (!apsAppDirectory.equals(newApsAppDirectory)) { - FileUtils.copyDirectory(apsAppDirectory, newApsAppDirectory); - apsAppDirectory = newApsAppDirectory; - } - - ApsAppTranslator translator = new ApsAppTranslator(this.apsAppName, apsAppDirectory, this.getApsApi(), true); - translator.execute(); - } catch (IOException ie) { - throw new MojoExecutionException("An I/O issue occurred", ie); - } catch (IllegalArgumentException iae) { - throw new MojoExecutionException("The input is not supported", iae); - } catch (IllegalStateException ise) { - throw new MojoExecutionException("The state of system is not supported", ise); - } - } - - protected File validateApsAppDirectory() throws MojoExecutionException { - if (!this.sourceDirectory.exists()) - throw new MojoExecutionException("The 'sourceDirectory' does not exist: " + this.sourceDirectory); - if (!this.sourceDirectory.isDirectory()) - throw new MojoExecutionException("The 'sourceDirectory' is not a directory: " + this.sourceDirectory); - - File appDirectory = new File(this.sourceDirectory, this.apsAppName); - - if (!appDirectory.exists()) - throw new MojoExecutionException("The 'apsAppName' does not exist in the 'apsAppsDirectory': " + appDirectory); - if (!appDirectory.isDirectory()) - throw new MojoExecutionException("The 'apsAppName' is not a directory in the 'apsAppsDirectory': " + appDirectory); - - return appDirectory; - } - - protected File validateTargetDirectory() throws MojoExecutionException { - if (!this.targetDirectory.exists()) { - this.targetDirectory.mkdirs(); - } else if (!this.targetDirectory.isDirectory()) { - throw new MojoExecutionException("The 'targetDirectory' is not a directory: " + this.targetDirectory); - } - - File appDirectory = new File(this.targetDirectory, this.apsAppName); - - if (!appDirectory.exists()) { - appDirectory.mkdirs(); - } else if (!appDirectory.isDirectory()) { - throw new MojoExecutionException("The 'apsAppName' is not a directory in the 'targetDirectory': " + appDirectory); - } - - return appDirectory; - } - -} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawlable.java b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawlable.java new file mode 100644 index 0000000..390da7f --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawlable.java @@ -0,0 +1,13 @@ +package com.inteligr8.maven.aps.modeling.crawler; + +public interface ApsAppCrawlable { + + ApsFileTransformer getAppJsonTransformer(); + + ApsFileTransformer getProcessJsonTransformer(); + + ApsFileTransformer getProcessBpmnTransformer(); + + ApsFileTransformer getFormJsonTransformer(); + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawler.java b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawler.java new file mode 100644 index 0000000..9639572 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsAppCrawler.java @@ -0,0 +1,105 @@ +package com.inteligr8.maven.aps.modeling.crawler; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApsAppCrawler { + + private final Logger logger = LoggerFactory.getLogger(ApsAppCrawler.class); + private final Pattern filenamePattern = Pattern.compile("([^/]+)-([0-9]+)\\.(json|bpmn20\\.xml)"); + private final String appName; + private final File appDirectory; + private final File appDescriptor; + private final boolean failOnIntegrityViolation; + + public ApsAppCrawler(String apsAppName, File apsAppDirectory, boolean failOnIntegrityViolation) { + this.appName = apsAppName; + this.appDirectory = apsAppDirectory; + this.failOnIntegrityViolation = failOnIntegrityViolation; + + this.appDescriptor = this.validateDescriptor(appDirectory); + if (this.logger.isDebugEnabled()) + this.logger.debug("APS App descriptor found: " + this.appDescriptor); + } + + protected File validateDescriptor(File appDirectory) { + File appDescriptor = new File(appDirectory, this.appName + ".json"); + + if (!appDescriptor.exists()) + throw new IllegalStateException("The APS App descriptor could not be found: " + appDescriptor); + if (!appDescriptor.isFile()) + throw new IllegalStateException("The APS App descriptor is not a file: " + appDescriptor); + + return appDescriptor; + } + + public void execute(ApsAppCrawlable crawlable) throws IOException { + this.logger.info("Crawling APS App ..."); + + Map processTransformers = new HashMap<>(); + processTransformers.put("json", crawlable.getProcessJsonTransformer()); + processTransformers.put("bpmn20.xml", crawlable.getProcessBpmnTransformer()); + processTransformers.put("bpmn", crawlable.getProcessBpmnTransformer()); + + this.transform(crawlable.getAppJsonTransformer(), this.appDescriptor, this.appName, null); + this.crawlModels("form-models", crawlable.getFormJsonTransformer()); + this.crawlModels("bpmn-models", processTransformers); + this.crawlModels("bpmn-subprocess-models", crawlable.getProcessJsonTransformer()); + } + + protected void crawlModels(String modelsDirectoryName, ApsFileTransformer transformer) throws IOException { + this.crawlModels(modelsDirectoryName, Collections.singletonMap("json", transformer)); + } + + protected void crawlModels(String modelsDirectoryName, Map transformers) throws IOException { + File modelsDirectory = new File(this.appDirectory, modelsDirectoryName); + if (!modelsDirectory.exists()) { + this.logger.debug("APS model directory doesn't exist; no models to transform: {}", modelsDirectory); + return; + } + + for (File modelFile : modelsDirectory.listFiles()) { + this.logger.trace("Transforming model: {}", modelFile); + + Matcher matcher = this.filenamePattern.matcher(modelFile.getName()); + if (!matcher.find()) { + this.logger.warn("The '{}' folder has a file with an unexpected filename format; skipping: {}", modelsDirectoryName, modelFile); + continue; + } + + String ext = matcher.group(3); + ApsFileTransformer transformer = transformers.get(ext.toLowerCase()); + if (transformer == null) { + this.logger.warn("The '{}' folder has a file with an unexpected extension; skipping: {}", modelsDirectoryName, modelFile); + continue; + } + + String modelName = matcher.group(1); + Long modelId = new Long(matcher.group(2)); + this.logger.trace("Transforming model {} ID: {}", modelName, modelId); + + this.transform(transformer, modelFile, modelName, modelId); + } + } + + private void transform(ApsFileTransformer transformer, File modelFile, String modelName, Long modelId) throws IOException { + try { + transformer.transformFile(modelFile, modelName, modelId); + } catch (IllegalArgumentException | IllegalStateException ie) { + if (this.failOnIntegrityViolation) { + throw ie; + } else { + this.logger.warn(ie.getMessage()); + } + } + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsFileTransformer.java b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsFileTransformer.java new file mode 100644 index 0000000..8e9ac56 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/crawler/ApsFileTransformer.java @@ -0,0 +1,10 @@ +package com.inteligr8.maven.aps.modeling.crawler; + +import java.io.File; +import java.io.IOException; + +public interface ApsFileTransformer { + + void transformFile(File file, String modelName, Long modelId) throws IOException; + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAddressibleGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAddressibleGoal.java new file mode 100644 index 0000000..6393015 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAddressibleGoal.java @@ -0,0 +1,99 @@ +package com.inteligr8.maven.aps.modeling.goal; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.settings.Server; + +import com.inteligr8.alfresco.activiti.ApsClientConfiguration; +import com.inteligr8.alfresco.activiti.ApsClientJerseyImpl; +import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; + +public abstract class ApsAddressibleGoal extends DisablableGoal { + + @Parameter( defaultValue = "${session}", readonly = true ) + protected MavenSession session; + + @Parameter( property = "aps-model.baseUrl", required = true, defaultValue = "http://localhost:8080/activiti-app" ) + protected String activitiAppBaseUrl; + + @Parameter( property = "aps-model.authType", required = true, defaultValue = "BASIC" ) + protected String activitiAppAuthType; + + @Parameter( property = "aps-model.basicAuth.mavenServerId", required = true, defaultValue = "aps" ) + protected String activitiAppAuthBasicServerId; + + @Parameter( property = "aps-model.oauth.code", required = false ) + protected String oauthCode; + + @Parameter( property = "aps-model.oauth.client.mavenServerId", required = false ) + protected String oauthClientServerId; + + @Parameter( property = "aps-model.oauth.mavenServerId", required = false ) + protected String oauthServerId; + + @Parameter( property = "aps-model.oauth.tokenUrl", required = false ) + protected String oauthTokenUrl; + + private ApsPublicRestApiJerseyImpl api; + + public ApsClientConfiguration getApsClientConfiguration() { + this.getLog().debug("Configuring APS to URL: " + this.activitiAppBaseUrl); + ApsClientConfiguration config = new ApsClientConfiguration(); + config.setBaseUrl(this.activitiAppBaseUrl); + switch (this.activitiAppAuthType.toUpperCase()) { + case "BASIC": + this.getLog().info("Configuring APS with BASIC authentication"); + + Server creds = this.session.getSettings().getServer(this.activitiAppAuthBasicServerId); + if (this.activitiAppAuthBasicServerId != null && creds == null) + this.getLog().warn("The Maven configuration has no server '" + this.activitiAppAuthBasicServerId + "'; continuing with default credentials"); + + if (creds != null) { + this.getLog().debug("Username: " + creds.getUsername()); + config.setBasicAuthUsername(creds.getUsername()); + config.setBasicAuthPassword(creds.getPassword()); + } + break; + case "OAUTH": + this.getLog().info("Configuring APS with OAuth authentication"); + + Server clientCreds = this.session.getSettings().getServer(this.oauthClientServerId); + Server oauthCreds = this.session.getSettings().getServer(this.oauthServerId); + 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 (clientCreds != null) { + this.getLog().debug("OAuth Client ID: " + clientCreds.getUsername()); + config.setOAuthClientId(clientCreds.getUsername()); + config.setOAuthClientSecret(clientCreds.getPassword()); + } + + if (oauthCreds != null) { + this.getLog().debug("OAuth Username: " + oauthCreds.getUsername()); + config.setOAuthUsername(oauthCreds.getUsername()); + config.setOAuthPassword(oauthCreds.getPassword()); + } + + config.setOAuthTokenUrl(this.oauthTokenUrl); + break; + default: + throw new IllegalArgumentException("The 'activitiAppAuthType' configuration must be either 'Basic' or 'OAuth'"); + } + + return config; + } + + public synchronized ApsPublicRestApiJerseyImpl getApsApi() { + if (this.api == null) { + ApsClientConfiguration config = this.getApsClientConfiguration(); + ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config); + this.api = new ApsPublicRestApiJerseyImpl(apsClient); + } + + return this.api; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppAccessibleGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppAccessibleGoal.java new file mode 100644 index 0000000..489852a --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppAccessibleGoal.java @@ -0,0 +1,57 @@ +package com.inteligr8.maven.aps.modeling.goal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Parameter; + +import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; +import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType; +import com.inteligr8.alfresco.activiti.model.Datum; +import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation; +import com.inteligr8.alfresco.activiti.model.Tenant; + +public abstract class ApsAppAccessibleGoal extends ApsAddressibleGoal { + + @Parameter( property = "aps-model.appName", required = true ) + protected String apsAppName; + + protected Long findTenantId() { + List tenants = this.getApsApi().getAdminApi().getTenants(); + if (tenants == null || tenants.isEmpty()) + return null; + return tenants.iterator().next().getId(); + } + + protected Map findAppNameIds() { + ApsPublicRestApiJerseyImpl api = this.getApsApi(); + + Map apps = new HashMap<>(16); + + this.getLog().debug("Searching for all APS Apps"); + ResultListDataRepresentation results = api.getModelsApi().get("everyone", null, ModelType.App.getId(), null); + this.getLog().debug("Found " + results.getTotal() + " APS Apps"); + for (Datum datum : results.getData()) { + String name = (String)datum.getAdditionalProperties().get("name"); + Number id = (Number)datum.getAdditionalProperties().get("id"); + apps.put(name, id.longValue()); + } + + return apps; + } + + protected Long findAppId() throws MojoExecutionException { + return this.findAppIdByName(this.apsAppName); + } + + protected Long findAppIdByName(String appName) throws MojoExecutionException { + Map apps = this.findAppNameIds(); + Long appId = apps.get(this.apsAppName); + if (appId == null) + throw new MojoExecutionException("The APS App '' could not be found; valid apps: " + apps.keySet()); + return appId; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppGoal.java new file mode 100644 index 0000000..e60b081 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsAppGoal.java @@ -0,0 +1,10 @@ +package com.inteligr8.maven.aps.modeling.goal; + +import org.apache.maven.plugins.annotations.Parameter; + +public abstract class ApsAppGoal extends DisablableGoal { + + @Parameter( property = "aps-model.appName", required = true ) + protected String apsAppName; + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsInfoGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsInfoGoal.java similarity index 85% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsInfoGoal.java rename to src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsInfoGoal.java index 07b8d01..d3764d5 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsInfoGoal.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsInfoGoal.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.goal; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -12,7 +12,7 @@ import com.inteligr8.alfresco.activiti.model.AppVersion; public class ApsInfoGoal extends ApsAddressibleGoal { @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void executeEnabled() throws MojoExecutionException, MojoFailureException { AppVersion version = this.getApsApi() .getAppVersionApi() .get(); diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/DeployAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DeployAppGoal.java similarity index 61% rename from src/main/java/com/inteligr8/maven/aps/modeling/DeployAppGoal.java rename to src/main/java/com/inteligr8/maven/aps/modeling/goal/DeployAppGoal.java index 07eee07..2f8540e 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/DeployAppGoal.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DeployAppGoal.java @@ -1,16 +1,19 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.goal; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import javax.ws.rs.core.MediaType; + 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 org.glassfish.jersey.media.multipart.FormDataContentDisposition; +import org.glassfish.jersey.media.multipart.FormDataMultiPart; +import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation; @@ -19,43 +22,39 @@ import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresenta @Component( role = org.apache.maven.plugin.Mojo.class ) public class DeployAppGoal extends ApsAppAccessibleGoal { - @Parameter( property = "apsAppName", required = true ) - protected String apsAppName; - - @Parameter( property = "sourceDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File sourceDirectory; + @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 = "skip", required = true, defaultValue = "false" ) - protected boolean skip = false; - protected final int bufferSize = 128 * 1024; + private final MediaType zipMediaType = MediaType.valueOf("application/zip"); @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void executeEnabled() throws MojoExecutionException, MojoFailureException { File sourceFile = this.validateSourceDirectory(); + Long appId = this.findAppId(); + try { - Long appId = this.findAppByName(this.apsAppName); this.uploadApp(appId, sourceFile); } catch (IOException ie) { - throw new MojoExecutionException("The downloaded APS App could not be unpacked", ie); + throw new MojoExecutionException("The APS App could not be uploaded", ie); } } protected File validateSourceDirectory() { - if (!this.sourceDirectory.exists()) { - throw new IllegalStateException("The 'sourceDirectory' does not exist: " + this.sourceDirectory); - } else if (!this.sourceDirectory.isDirectory()) { - throw new IllegalStateException("The 'sourceDirectory' is not a directory: " + this.sourceDirectory); + if (!this.zipDirectory.exists()) { + throw new IllegalStateException("The 'zipDirectory' does not exist: " + this.zipDirectory); + } else if (!this.zipDirectory.isDirectory()) { + throw new IllegalStateException("The 'zipDirectory' is not a directory: " + this.zipDirectory); } - File sourceFile = new File(this.sourceDirectory, this.apsAppName + ".zip"); + File sourceFile = new File(this.zipDirectory, this.apsAppName + ".zip"); if (!sourceFile.exists()) { - throw new IllegalStateException("The App file does not exists in the 'sourceDirectory': " + sourceFile); + throw new IllegalStateException("The App file does not exists in the 'zipDirectory': " + sourceFile); } else if (!sourceFile.isFile()) { throw new IllegalStateException("The App file is not a file: " + sourceFile); } @@ -66,33 +65,31 @@ public class DeployAppGoal extends ApsAppAccessibleGoal { private void uploadApp(Long appId, File appZip) throws IOException, MojoExecutionException { ApsPublicRestApiJerseyImpl api = this.getApsApi(); - FormDataContentDisposition cdisposition = FormDataContentDisposition.name("file") - .size(appZip.length()) - .fileName(appZip.getName()) - .build(); - FileInputStream fistream = new FileInputStream(appZip); BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); try { + FormDataMultiPart multipart = new FormDataMultiPart(); + multipart.bodyPart(new StreamDataBodyPart("file", bistream, appZip.getName(), this.zipMediaType)); + if (appId == null) { if (this.publish) { this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName); - AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(bistream, cdisposition); + AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(multipart); if (Boolean.TRUE.equals(appDefUpdate.getError())) throw new MojoExecutionException(appDefUpdate.getErrorDescription()); } else { this.getLog().info("Uploading new APS App: " + this.apsAppName); - api.getAppDefinitionsJerseyApi().import_(bistream, cdisposition); + api.getAppDefinitionsJerseyApi().importApp(multipart, true); } } else { if (this.publish) { - this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName); - AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appId, bistream, cdisposition); + this.getLog().info("Uploading, versioning, & publishing APS App: " + this.apsAppName + " (" + appId + ")"); + AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(appId, multipart); if (Boolean.TRUE.equals(appDefUpdate.getError())) throw new MojoExecutionException(appDefUpdate.getErrorDescription()); } else { - this.getLog().info("Uploading & versioning APS App: " + this.apsAppName); - api.getAppDefinitionsJerseyApi().import_(appId, bistream, cdisposition); + this.getLog().info("Uploading & versioning APS App: " + this.apsAppName + " (" + appId + ")"); + api.getAppDefinitionsJerseyApi().importApp(appId, multipart, true); } } } finally { diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/DisablableGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DisablableGoal.java new file mode 100644 index 0000000..ba1c368 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DisablableGoal.java @@ -0,0 +1,29 @@ +package com.inteligr8.maven.aps.modeling.goal; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Parameter; + +public abstract class DisablableGoal extends AbstractMojo { + + @Parameter( property = "aps-model.skip", required = true, defaultValue = "false" ) + protected boolean skip = false; + + protected boolean isEnabled() { + return !this.skip; + } + + protected boolean isDisabled() { + return this.skip; + } + + @Override + public final void execute() throws MojoExecutionException, MojoFailureException { + if (this.isEnabled()) + this.executeEnabled(); + } + + public abstract void executeEnabled() throws MojoExecutionException, MojoFailureException; + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/DownloadAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DownloadAppGoal.java similarity index 61% rename from src/main/java/com/inteligr8/maven/aps/modeling/DownloadAppGoal.java rename to src/main/java/com/inteligr8/maven/aps/modeling/goal/DownloadAppGoal.java index ce95e70..3b7001b 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/DownloadAppGoal.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/DownloadAppGoal.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.goal; import java.io.File; import java.io.IOException; @@ -16,23 +16,17 @@ import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; @Component( role = org.apache.maven.plugin.Mojo.class ) public class DownloadAppGoal extends ApsAppAccessibleGoal { - @Parameter( property = "apsAppName", required = true ) - protected String apsAppName; - - @Parameter( property = "targetDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File targetDirectory; - - @Parameter( property = "skip", required = true, defaultValue = "false" ) - protected boolean skip = false; + @Parameter( property = "aps-model.download.directory", required = true, defaultValue = "${project.build.directory}/aps" ) + protected File zipDirectory; @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void executeEnabled() throws MojoExecutionException, MojoFailureException { this.validateTargetDirectory(); - long appId = this.findAppByName(this.apsAppName); + Long appId = this.findAppId(); File appZip = this.downloadApp(appId); - File toAppZip = new File(this.targetDirectory, this.apsAppName + ".zip"); + File toAppZip = new File(this.zipDirectory, this.apsAppName + ".zip"); if (toAppZip.exists()) toAppZip.delete(); @@ -44,10 +38,10 @@ public class DownloadAppGoal extends ApsAppAccessibleGoal { } protected void validateTargetDirectory() { - if (!this.targetDirectory.exists()) { - this.getLog().debug("Creating APS Apps directory: " + this.targetDirectory); - this.targetDirectory.mkdirs(); - } else if (!this.targetDirectory.isDirectory()) { + if (!this.zipDirectory.exists()) { + 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"); } } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/PackAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/PackAppGoal.java similarity index 65% rename from src/main/java/com/inteligr8/maven/aps/modeling/PackAppGoal.java rename to src/main/java/com/inteligr8/maven/aps/modeling/goal/PackAppGoal.java index fb24f77..69bf6c1 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/PackAppGoal.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/PackAppGoal.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.goal; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -12,7 +12,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.commons.io.IOUtils; -import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; @@ -21,24 +20,18 @@ import org.codehaus.plexus.component.annotations.Component; @Mojo( name = "pack-app", threadSafe = true ) @Component( role = org.apache.maven.plugin.Mojo.class ) -public class PackAppGoal extends AbstractMojo { +public class PackAppGoal extends ApsAppGoal { - @Parameter( property = "apsAppName", required = true ) - protected String apsAppName; - - @Parameter( property = "sourceDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File sourceDirectory; - - @Parameter( property = "targetDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File targetDirectory; + @Parameter( property = "aps-model.app.directory", required = true, defaultValue = "${project.build.directory}/aps/app" ) + protected File unzipDirectory; - @Parameter( property = "skip", required = true, defaultValue = "false" ) - protected boolean skip = false; + @Parameter( property = "aps-model.upload.directory", required = true, defaultValue = "${project.build.directory}/aps" ) + protected File zipDirectory; protected final int bufferSize = 128 * 1024; @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void executeEnabled() throws MojoExecutionException, MojoFailureException { this.validateSourceDirectory(); File appDirectory = this.validateAppDirectory(); @@ -52,17 +45,17 @@ public class PackAppGoal extends AbstractMojo { } protected void validateSourceDirectory() { - if (!this.sourceDirectory.exists()) { - throw new IllegalStateException("The 'sourceDirectory' does not exist: " + this.sourceDirectory); - } else if (!this.sourceDirectory.isDirectory()) { - throw new IllegalStateException("The 'sourceDirectory' is not a directory: " + this.sourceDirectory); + if (!this.unzipDirectory.exists()) { + throw new IllegalStateException("The 'unzipDirectory' does not exist: " + this.unzipDirectory); + } else if (!this.unzipDirectory.isDirectory()) { + throw new IllegalStateException("The 'unzipDirectory' is not a directory: " + this.unzipDirectory); } } protected File validateAppDirectory() { - File appDirectory = new File(this.sourceDirectory, this.apsAppName); + File appDirectory = new File(this.unzipDirectory, this.apsAppName); if (!appDirectory.exists()) { - throw new IllegalStateException("The 'apsAppName' does not exist in the source directory: " + this.sourceDirectory); + throw new IllegalStateException("The 'apsAppName' does not exist in the source directory: " + this.unzipDirectory); } else if (!appDirectory.isDirectory()) { throw new IllegalStateException("The 'apsAppName' refers to a file and not a directory: " + appDirectory); } @@ -71,19 +64,19 @@ public class PackAppGoal extends AbstractMojo { } protected File validateTargetDirectory() { - if (!this.targetDirectory.exists()) { - this.getLog().debug("The 'targetDirectory' does not exist; creating: " + this.targetDirectory); - this.targetDirectory.mkdirs(); - } else if (!this.targetDirectory.isDirectory()) { - throw new IllegalStateException("The 'targetDirectory' is not a directory: " + this.targetDirectory); + if (!this.zipDirectory.exists()) { + this.getLog().debug("The 'zipDirectory' does not exist; creating: " + this.zipDirectory); + this.zipDirectory.mkdirs(); + } else if (!this.zipDirectory.isDirectory()) { + throw new IllegalStateException("The 'zipDirectory' is not a directory: " + this.zipDirectory); } - File targetFile = new File(this.targetDirectory, this.apsAppName + ".zip"); + File targetFile = new File(this.zipDirectory, this.apsAppName + ".zip"); if (targetFile.isDirectory()) { throw new IllegalStateException("The App file is not a file: " + targetFile); } else if (targetFile.exists()) { - this.getLog().debug("The App file in the 'targetDirectory' exists; deleting: " + targetFile); + this.getLog().debug("The App file in the 'zipDirectory' exists; deleting: " + targetFile); targetFile.delete(); } @@ -99,6 +92,7 @@ public class PackAppGoal extends AbstractMojo { ZipOutputStream zostream = new ZipOutputStream(bostream); try { this.zipDirectory(appDirectory, null, zostream); + zostream.flush(); } finally { zostream.close(); } @@ -125,7 +119,8 @@ public class PackAppGoal extends AbstractMojo { this.getLog().debug("Packing APS file: " + path); - ZipEntry zentry = new ZipEntry(path.toString()); + // Activit App processing requires forward slashes (WOW) + ZipEntry zentry = new ZipEntry(path.toString().replace('\\', '/')); zostream.putNextEntry(zentry); try { FileInputStream fistream = new FileInputStream(file); diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/TranslateAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/TranslateAppGoal.java new file mode 100644 index 0000000..126a899 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/TranslateAppGoal.java @@ -0,0 +1,91 @@ +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.ApsAppCrawler; +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 { + + @Parameter( property = "aps-model.app.directory", required = true, defaultValue = "${project.build.directory}/aps/app" ) + protected File unzipDirectory; + + @Parameter( property = "aps-model.translatedApp.directory", required = false, defaultValue = "${project.build.directory}/aps/app" ) + 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 { + File apsAppDirectory = this.validateApsAppDirectory(); + File newApsAppDirectory = this.validateTargetDirectory(); + + try { + if (!apsAppDirectory.equals(newApsAppDirectory)) { + FileUtils.copyDirectory(apsAppDirectory, newApsAppDirectory); + apsAppDirectory = newApsAppDirectory; + } + + ApsAppTranslator translator = new ApsAppTranslator(apsAppDirectory, this.getApsApi()); + translator.buildIndexes(); + + ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, apsAppDirectory, true); + crawler.execute(translator); + } catch (IOException ie) { + throw new MojoExecutionException("An I/O issue occurred", ie); + } catch (IllegalArgumentException iae) { + throw new MojoExecutionException("The input is not supported", iae); + } catch (IllegalStateException ise) { + throw new MojoExecutionException("The state of system is not supported", ise); + } + } + + protected File validateApsAppDirectory() throws MojoExecutionException { + if (!this.unzipDirectory.exists()) + throw new MojoExecutionException("The 'unzipDirectory' does not exist: " + this.unzipDirectory); + if (!this.unzipDirectory.isDirectory()) + throw new MojoExecutionException("The 'unzipDirectory' is not a directory: " + this.unzipDirectory); + + File appDirectory = new File(this.unzipDirectory, this.apsAppName); + + if (!appDirectory.exists()) + throw new MojoExecutionException("The 'apsAppName' does not exist in the 'unzipDirectory': " + appDirectory); + if (!appDirectory.isDirectory()) + throw new MojoExecutionException("The 'apsAppName' is not a directory in the 'unzipDirectory': " + appDirectory); + + return appDirectory; + } + + protected File 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); + } + + File appDirectory = new File(this.finalDirectory, this.apsAppName); + + if (!appDirectory.exists()) { + appDirectory.mkdirs(); + } else if (!appDirectory.isDirectory()) { + throw new MojoExecutionException("The 'apsAppName' is not a directory in the 'finalDirectory': " + appDirectory); + } + + return appDirectory; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/UnpackAppGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/UnpackAppGoal.java similarity index 58% rename from src/main/java/com/inteligr8/maven/aps/modeling/UnpackAppGoal.java rename to src/main/java/com/inteligr8/maven/aps/modeling/goal/UnpackAppGoal.java index ed755e0..062e68e 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/UnpackAppGoal.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/UnpackAppGoal.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.goal; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -10,43 +10,53 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.xml.transform.TransformerException; import org.apache.commons.io.IOUtils; -import org.apache.maven.plugin.AbstractMojo; 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 org.w3c.dom.Document; import org.xml.sax.SAXException; +import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawler; +import com.inteligr8.maven.aps.modeling.normalizer.ApsAppNormalizer; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; + @Mojo( name = "unpack-app", threadSafe = true ) @Component( role = org.apache.maven.plugin.Mojo.class ) -public class UnpackAppGoal extends AbstractMojo { +public class UnpackAppGoal extends ApsAppGoal { - @Parameter( property = "apsAppName", required = true ) - protected String apsAppName; + @Parameter( property = "aps-model.download.directory", required = true, defaultValue = "${project.build.directory}/aps" ) + protected File zipDirectory; - @Parameter( property = "sourceDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File sourceDirectory; - - @Parameter( property = "targetDirectory", required = true, defaultValue = "${project.build.directory}/app" ) - protected File targetDirectory; + @Parameter( property = "aps-model.app.directory", required = true, defaultValue = "${project.build.directory}/aps/app" ) + protected File unzipDirectory; - @Parameter( property = "charset", required = true, defaultValue = "utf-8" ) + @Parameter( property = "aps-model.reformat", required = true, defaultValue = "true" ) + protected boolean reformat = true; + + @Parameter( property = "aps-model.normalize" ) + protected boolean normalize; + + @Parameter( property = "aps-model.normalize.diffFriendly" ) + protected boolean diffFriendly; + + @Parameter( property = "aps-model.charset", required = true, defaultValue = "utf-8" ) protected String charsetName = "utf-8"; - @Parameter( property = "skip", required = true, defaultValue = "false" ) - protected boolean skip = false; - private final int bufferSize = 128 * 1024; @Override - public void execute() throws MojoExecutionException, MojoFailureException { + public void executeEnabled() throws MojoExecutionException, MojoFailureException { + this.validate(); + File sourceFile = this.validateSourceDirectory(); this.validateTargetDirectory(); @@ -60,20 +70,37 @@ public class UnpackAppGoal extends AbstractMojo { throw new MojoExecutionException("The downloaded APS App could not be unpacked", ie); } - this.normalizeFiles(appDirectory); + if (this.reformat) + this.reformatFiles(appDirectory); + if (this.normalize) { + try { + ApsAppNormalizer normalizer = new ApsAppNormalizer(this.diffFriendly); + ApsAppCrawler crawler = new ApsAppCrawler(this.apsAppName, appDirectory, true); + crawler.execute(normalizer); + } catch (IOException ie) { + throw new MojoExecutionException("An I/O issue occurred", ie); + } + } + } + + protected void validate() { + if (this.normalize && !this.reformat) { + this.getLog().warn("The reformat option is disabled, but normalization is enabled; enabling reformat anyway"); + this.reformat = true; + } } protected File validateSourceDirectory() { - if (!this.sourceDirectory.exists()) { - throw new IllegalStateException("The 'sourceDirectory' does not exist: " + this.sourceDirectory); - } else if (!this.sourceDirectory.isDirectory()) { - throw new IllegalStateException("The 'sourceDirectory' is not a directory: " + this.sourceDirectory); + if (!this.zipDirectory.exists()) { + throw new IllegalStateException("The 'zipDirectory' does not exist: " + this.zipDirectory); + } else if (!this.zipDirectory.isDirectory()) { + throw new IllegalStateException("The 'zipDirectory' is not a directory: " + this.zipDirectory); } - File sourceFile = new File(this.sourceDirectory, this.apsAppName + ".zip"); + File sourceFile = new File(this.zipDirectory, this.apsAppName + ".zip"); if (!sourceFile.exists()) { - throw new IllegalStateException("The App file does not exists in the 'sourceDirectory': " + sourceFile); + throw new IllegalStateException("The App file does not exists in the 'zipDirectory': " + sourceFile); } else if (!sourceFile.isFile()) { throw new IllegalStateException("The App file is not a file: " + sourceFile); } @@ -82,16 +109,16 @@ public class UnpackAppGoal extends AbstractMojo { } protected void validateTargetDirectory() { - if (!this.targetDirectory.exists()) { - this.getLog().debug("Creating APS Apps directory: " + this.targetDirectory); - this.targetDirectory.mkdirs(); - } else if (!this.targetDirectory.isDirectory()) { - throw new IllegalStateException("The 'apsAppsDirectory' refers to a file and not a directory: " + this.targetDirectory); + if (!this.unzipDirectory.exists()) { + this.getLog().debug("Creating unzip directory: " + this.unzipDirectory); + this.unzipDirectory.mkdirs(); + } else if (!this.unzipDirectory.isDirectory()) { + throw new IllegalStateException("The 'unzipDirectory' refers to a file and not a directory: " + this.unzipDirectory); } } protected File validateAppDirectory() { - File appDirectory = new File(this.targetDirectory, this.apsAppName); + File appDirectory = new File(this.unzipDirectory, this.apsAppName); if (!appDirectory.exists()) { this.getLog().debug("Creating APS App directory: " + appDirectory); appDirectory.mkdirs(); @@ -149,21 +176,25 @@ public class UnpackAppGoal extends AbstractMojo { } } - private void normalizeFiles(File directory) throws MojoFailureException { + private void reformatFiles(File directory) throws MojoFailureException { for (File file : directory.listFiles()) { if (file.isDirectory()) { - this.normalizeFiles(file); + this.reformatFiles(file); } else { String ext = this.getFileExtension(file); try { switch (ext) { case "json": - ModelUtil.getInstance().json(file); + this.getLog().debug("Reformatting file: " + file); + Map json = ModelUtil.getInstance().readJsonAsMap(file); + ModelUtil.getInstance().writeJson(json, file, false); break; case "xml": case "bpmn": - ModelUtil.getInstance().xml(file); + this.getLog().debug("Reformatting file: " + file); + Document xml = ModelUtil.getInstance().readXml(file); + ModelUtil.getInstance().writeXml(xml, file); break; default: file.delete(); diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppJsonNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppJsonNormalizer.java new file mode 100644 index 0000000..a7277e7 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppJsonNormalizer.java @@ -0,0 +1,53 @@ +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.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.inteligr8.maven.aps.modeling.util.ApsModelArrayNodeSorter; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; + +public class ApsAppJsonNormalizer implements ApsFileNormalizer { + + private final Logger logger = LoggerFactory.getLogger(ApsAppJsonNormalizer.class); + + private final boolean enableSorting; + + public ApsAppJsonNormalizer(boolean enableSorting) { + this.enableSorting = enableSorting; + } + + @Override + public void transformFile(File file, String modelName, Long modelId) throws IOException { + this.logger.debug("Normalizing App JSON file: {}", file); + + boolean changed = false; + JsonNode descriptor = ModelUtil.getInstance().readJson(file); + ArrayNode jsonModels = (ArrayNode)descriptor.get("definition").get("models"); + + // remove system-level and non-functional fields from models + for (JsonNode _jsonModel : jsonModels) { + ObjectNode jsonModel = (ObjectNode)_jsonModel; + + int fields = jsonModel.size(); + jsonModel.remove(Arrays.asList("lastUpdated")); +// jsonModel.remove(Arrays.asList("createdBy", "createdByFullName", "lastUpdatedBy", "lastUpdatedByFullName", "lastUpdated")); + if (jsonModel.size() < fields) + changed = true; + } + + // sort the models for better 'diff' support + if (this.enableSorting) + changed = ApsModelArrayNodeSorter.getInstance().sort(jsonModels, "name") || changed; + + if (changed) + ModelUtil.getInstance().writeJson(descriptor, file, this.enableSorting); + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppNormalizer.java new file mode 100644 index 0000000..6e607db --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsAppNormalizer.java @@ -0,0 +1,34 @@ +package com.inteligr8.maven.aps.modeling.normalizer; + +import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawlable; +import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer; + +public class ApsAppNormalizer implements ApsAppCrawlable { + + private final boolean enableSorting; + + public ApsAppNormalizer(boolean enableSorting) { + this.enableSorting = enableSorting; + } + + @Override + public ApsFileTransformer getAppJsonTransformer() { + return new ApsAppJsonNormalizer(this.enableSorting); + } + + @Override + public ApsFileTransformer getFormJsonTransformer() { + return new ApsFormJsonNormalizer(); + } + + @Override + public ApsFileTransformer getProcessJsonTransformer() { + return new ApsProcessJsonNormalizer(this.enableSorting); + } + + @Override + public ApsFileTransformer getProcessBpmnTransformer() { + return new ApsProcessBpmnNormalizer(); + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFileNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFileNormalizer.java new file mode 100644 index 0000000..9ff8d1f --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFileNormalizer.java @@ -0,0 +1,7 @@ +package com.inteligr8.maven.aps.modeling.normalizer; + +import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer; + +public interface ApsFileNormalizer extends ApsFileTransformer { + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFormJsonNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFormJsonNormalizer.java new file mode 100644 index 0000000..5a96d51 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsFormJsonNormalizer.java @@ -0,0 +1,19 @@ +package com.inteligr8.maven.aps.modeling.normalizer; + +import java.io.File; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApsFormJsonNormalizer implements ApsFileNormalizer { + + private final Logger logger = LoggerFactory.getLogger(ApsFormJsonNormalizer.class); + + @Override + public void transformFile(File file, String modelName, Long modelId) throws IOException { + this.logger.debug("Normalizing Form JSON file: {}", file); + this.logger.trace("Nothing to normalize: {}", file); + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessBpmnNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessBpmnNormalizer.java new file mode 100644 index 0000000..9120c45 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessBpmnNormalizer.java @@ -0,0 +1,52 @@ +package com.inteligr8.maven.aps.modeling.normalizer; + +import java.io.File; +import java.io.IOException; + +import javax.xml.transform.TransformerException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.inteligr8.maven.aps.modeling.util.ModelUtil; + +public class ApsProcessBpmnNormalizer implements ApsFileNormalizer { + + private static final String NAMESPACE_ACTIVITI_MODELER = "http://activiti.com/modeler"; + + private final Logger logger = LoggerFactory.getLogger(ApsProcessBpmnNormalizer.class); + + @Override + public void transformFile(File file, String modelName, Long modelId) throws IOException { + this.logger.debug("Normalizing Process BPMN file: {}", file); + + boolean changed = false; + + try { + Document bpmn = ModelUtil.getInstance().readXml(file); + Element definitionsElement = bpmn.getDocumentElement(); + + changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "exportDateTime") || changed; + changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelLastUpdated") || changed; + + if (changed) + ModelUtil.getInstance().writeXml(bpmn, file); + } catch (TransformerException | SAXException e) { + throw new IllegalStateException("An XML issue occurred", e); + } + } + + private boolean removeAttributeIfSet(Element element, String attrNamespace, String attrName) { + Attr attr = element.getAttributeNodeNS(attrNamespace, attrName); + if (attr == null) + return false; + + element.removeAttributeNode(attr); + return true; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessJsonNormalizer.java b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessJsonNormalizer.java new file mode 100644 index 0000000..cc0e1ac --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/normalizer/ApsProcessJsonNormalizer.java @@ -0,0 +1,50 @@ +package com.inteligr8.maven.aps.modeling.normalizer; + +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.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.inteligr8.maven.aps.modeling.util.ApsModelArrayNodeSorter; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; + +public class ApsProcessJsonNormalizer implements ApsFileNormalizer { + + private final Logger logger = LoggerFactory.getLogger(ApsProcessJsonNormalizer.class); + + private final boolean enableSorting; + + public ApsProcessJsonNormalizer(boolean enableSorting) { + this.enableSorting = enableSorting; + } + + @Override + public void transformFile(File file, String modelName, Long modelId) throws IOException { + this.logger.debug("Normalizing Process JSON file: {}", file); + + if (!this.enableSorting) + return; + + boolean changed = false; + + ObjectNode jsonDescriptor = ModelUtil.getInstance().readJson(file, ObjectNode.class); + ArrayNode jsonChildShapes = this.getChildShapes(jsonDescriptor); + + changed = ApsModelArrayNodeSorter.getInstance().sort(jsonChildShapes, "resourceId") || changed; + + if (changed) + ModelUtil.getInstance().writeJson(jsonDescriptor, file, this.enableSorting); + } + + private ArrayNode getChildShapes(ObjectNode jsonDescriptor) { + JsonNode jsonChildShapes = jsonDescriptor.get("childShapes"); + if (jsonChildShapes == null) + jsonChildShapes = jsonDescriptor.get("editorJson").get("childShapes"); + return (ArrayNode)jsonChildShapes; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppJsonTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppJsonTranslator.java similarity index 80% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsAppJsonTranslator.java rename to src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppJsonTranslator.java index 6b947a5..95eccca 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppJsonTranslator.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppJsonTranslator.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.translator; import java.io.File; import java.io.IOException; @@ -10,6 +10,8 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.inteligr8.alfresco.activiti.model.Group; +import com.inteligr8.maven.aps.modeling.util.Index; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; public class ApsAppJsonTranslator implements ApsFileTranslator { @@ -32,10 +34,9 @@ public class ApsAppJsonTranslator implements ApsFileTranslator { @Override public void translateFile(File file, String modelName, Long modelId) throws IOException { this.logger.debug("Translating JSON file: {}", file); - - JsonNode descriptor = ModelUtil.getInstance().readJson(file); - + boolean changed = false; + JsonNode descriptor = ModelUtil.getInstance().readJson(file); for (JsonNode _jsonModel : descriptor.get("definition").get("models")) { ObjectNode jsonModel = (ObjectNode)_jsonModel; @@ -64,6 +65,15 @@ public class ApsAppJsonTranslator implements ApsFileTranslator { this.logger.trace("The process '{}' ID does not change; leaving unchanged", fileProcessName); } } + + if (jsonModel.has("version")) { + jsonModel.put("version", 0); + changed = true; + } +// int fields = jsonModel.size(); +// jsonModel.remove(Arrays.asList("version")); +// if (jsonModel.size() < fields) +// changed = true; } for (JsonNode _jsonIdentityInfo : descriptor.get("definition").get("publishIdentityInfo")) { @@ -77,20 +87,29 @@ public class ApsAppJsonTranslator implements ApsFileTranslator { long apsOrgId = this.apsOrgIndex.get(fileOrgName).getId(); if (fileOrgId != apsOrgId) { - this.logger.debug("The organziation '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId); + this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId); jsonGroup.put("id", apsOrgId); changed = true; } else { this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName); } + + if (jsonGroup.has("parentGroupId")) { + jsonGroup.put("parentGroupId", 0); + changed = true; + } +// if (jsonGroup.has("parentGroupId")) { +// jsonGroup.remove("parentGroupId"); +// changed = true; +// } } else { - this.logger.warn("The organziation '{}' does not exist in APS", fileOrgName); + this.logger.warn("The organization '{}' does not exist in APS", fileOrgName); // FIXME create the organization } } if (changed) - ModelUtil.getInstance().writeJson(descriptor, file); + ModelUtil.getInstance().writeJson(descriptor, file, false); } } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppTranslator.java similarity index 54% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsAppTranslator.java rename to src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppTranslator.java index 424d94e..3c0c363 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsAppTranslator.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsAppTranslator.java @@ -1,8 +1,7 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.translator; import java.io.File; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -13,23 +12,25 @@ import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.inteligr8.alfresco.activiti.ApsPublicRestApi; import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl; import com.inteligr8.alfresco.activiti.api.ModelsApi.ModelType; import com.inteligr8.alfresco.activiti.model.Datum; import com.inteligr8.alfresco.activiti.model.Group; import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation; import com.inteligr8.alfresco.activiti.model.Tenant; +import com.inteligr8.maven.aps.modeling.crawler.ApsAppCrawlable; +import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer; +import com.inteligr8.maven.aps.modeling.util.Index; -public class ApsAppTranslator { +public class ApsAppTranslator implements ApsAppCrawlable { private final Logger logger = LoggerFactory.getLogger(ApsAppTranslator.class); private final Pattern filenamePattern = Pattern.compile("([^/]+)-([0-9]+)\\.(json|bpmn20\\.xml)"); - private final ApsPublicRestApiJerseyImpl api; - private final String appName; + private final ApsPublicRestApi api; private final File appDirectory; - private final File appDescriptor; - private final boolean failOnIntegrityViolation; + private boolean indexesBuilt = false; private Map apsOrgIndex; private Index apsFormIndex; private Index apsProcessIndex; @@ -37,86 +38,98 @@ public class ApsAppTranslator { private Index fileProcessIndex; private Index fileSubprocessIndex; - public ApsAppTranslator(String apsAppName, File apsAppDirectory, ApsPublicRestApiJerseyImpl api, boolean failOnIntegrityViolation) { + public ApsAppTranslator(File apsAppDirectory, ApsPublicRestApiJerseyImpl api) { this.api = api; - this.appName = apsAppName; this.appDirectory = apsAppDirectory; - this.failOnIntegrityViolation = failOnIntegrityViolation; - - this.appDescriptor = this.validateDescriptor(appDirectory); - if (this.logger.isDebugEnabled()) - this.logger.debug("APS App descriptor found: " + this.appDescriptor); } - protected File validateDescriptor(File appDirectory) { - File appDescriptor = new File(appDirectory, this.appName + ".json"); + public synchronized void buildIndexes() throws IOException { + if (this.indexesBuilt) + return; - if (!appDescriptor.exists()) - throw new IllegalStateException("The APS App descriptor could not be found: " + appDescriptor); - if (!appDescriptor.isFile()) - throw new IllegalStateException("The APS App descriptor is not a file: " + appDescriptor); - - return appDescriptor; - } - - public void execute() throws IOException { - this.buildIndexes(); - this.translate(); - } - - protected void buildIndexes() throws IOException { this.logger.info("Building indexes ..."); long tenantId = this.findTenantId(); this.logger.debug("APS App tenant ID: {}", tenantId); this.apsOrgIndex = this.buildApsGroupMap(tenantId); - this.logger.debug("APS App organizations: {}", this.apsOrgIndex); + this.logLarge("APS App organizations: {}", this.apsOrgIndex); this.apsProcessIndex = this.buildApsModelIndex(ModelType.Process); - this.logger.debug("APS process models: {}", this.apsProcessIndex); + this.logLarge("APS process models: {}", this.apsProcessIndex); this.apsFormIndex = this.buildApsModelIndex(ModelType.Form); - this.logger.debug("APS form models: {}", this.apsFormIndex); + this.logLarge("APS form models: {}", this.apsFormIndex); this.fileFormIndex = this.buildFileIndex("form-models", this.apsFormIndex, true); - this.logger.debug("File form models: {}", this.fileFormIndex); + this.logLarge("File form models: {}", this.fileFormIndex); this.fileProcessIndex = this.buildFileIndex("bpmn-models", this.apsProcessIndex, true); - this.logger.debug("File process models: {}", this.fileProcessIndex); + this.logLarge("File process models: {}", this.fileProcessIndex); this.fileSubprocessIndex = this.buildFileIndex("bpmn-subprocess-models", this.apsProcessIndex, true); - this.logger.debug("File subprocess models: {}", this.fileSubprocessIndex); + this.logLarge("File subprocess models: {}", this.fileSubprocessIndex); + + this.indexesBuilt = true; } - protected void translate() throws IOException { - this.logger.info("Translating models ..."); + private void logLarge(String message, Map map) { + if (this.logger.isTraceEnabled()) { + this.logger.trace(message, map); + } else { + this.logger.debug(message, map.keySet()); + } + } + + private void logLarge(String message, Index index) { + if (this.logger.isTraceEnabled()) { + this.logger.trace(message, index); + } else { + this.logger.debug(message, index.valueSet()); + } + } + + @Override + public ApsFileTransformer getAppJsonTransformer() { + if (!this.indexesBuilt) + throw new IllegalStateException("The indexes are never built"); - ApsFileTranslator descriptorTranslator = new ApsAppJsonTranslator( + return new ApsAppJsonTranslator( this.apsOrgIndex, this.apsProcessIndex, this.fileProcessIndex); - ApsFileTranslator formTranslator = new ApsFormJsonTranslator( + } + + @Override + public ApsFileTransformer getFormJsonTransformer() { + if (!this.indexesBuilt) + throw new IllegalStateException("The indexes are never built"); + + return new ApsFormJsonTranslator( this.apsFormIndex); - ApsFileTranslator processTranslator = new ApsProcessJsonTranslator( + } + + @Override + public ApsFileTransformer getProcessJsonTransformer() { + if (!this.indexesBuilt) + throw new IllegalStateException("The indexes are never built"); + + return new ApsProcessJsonTranslator( this.apsProcessIndex, this.apsOrgIndex, this.apsFormIndex); - ApsFileTranslator bpmnTranslator = new ApsProcessBpmnTranslator( + } + + @Override + public ApsFileTransformer getProcessBpmnTransformer() { + if (!this.indexesBuilt) + throw new IllegalStateException("The indexes are never built"); + + return new ApsProcessBpmnTranslator( this.apsProcessIndex, this.apsOrgIndex, this.apsFormIndex, this.fileFormIndex); - - Map processTranslators = new HashMap<>(); - processTranslators.put("json", processTranslator); - processTranslators.put("bpmn20.xml", bpmnTranslator); - processTranslators.put("bpmn", bpmnTranslator); - - this.translate(descriptorTranslator, this.appDescriptor, this.appName, null); - this.translateModels("form-models", formTranslator); - this.translateModels("bpmn-models", processTranslators); - this.translateModels("bpmn-subprocess-models", processTranslator); } protected Long findTenantId() { @@ -134,7 +147,8 @@ public class ApsAppTranslator { } protected Index buildApsModelIndex(ModelType modelType) { - ResultListDataRepresentation results = this.api.getModelsApi().get(null, null, modelType.getId(), null); + ResultListDataRepresentation results = this.api.getModelsApi().get("everyone", null, modelType.getId(), null); + this.logger.debug("APS {} models found: {} out of {}", modelType, results.getSize(), results.getTotal()); Index map = new Index<>(results.getSize().intValue(), false); @@ -143,11 +157,13 @@ public class ApsAppTranslator { Number defId = (Number)datum.getAdditionalProperties().get("id"); String defName = (String)datum.getAdditionalProperties().get("name"); map.put(defId.longValue(), defName); + + // FIXME add paging support } } catch (IllegalArgumentException iae) { throw new IllegalStateException("This goal does not support the current state of APS", iae); } - + return map; } @@ -157,77 +173,50 @@ public class ApsAppTranslator { } protected Index buildFileIndex(File modelDirectory, Index apsModelIndex, boolean renameFiles) { - if (!modelDirectory.exists()) + if (!modelDirectory.exists()) { + this.logger.debug("APS model directory doesn't exist; creating empty index: {}", modelDirectory); return new Index<>(0, false); + } + if (!modelDirectory.isDirectory()) throw new IllegalStateException("The APS model directory is actually a file: " + modelDirectory); Index fileModelIndex = new Index<>(apsModelIndex.size(), false); for (File modelFile : modelDirectory.listFiles()) { + this.logger.trace("Building index for model file: {}", modelFile); + Matcher matcher = this.filenamePattern.matcher(modelFile.getName()); String modelName = matcher.replaceFirst("$1"); String modelIdStr = matcher.replaceFirst("$2"); String modelExt = matcher.replaceFirst("$3"); long fileModelId = Long.parseLong(modelIdStr); + this.logger.trace("Building index for model {} ID: {}", modelName, fileModelId); fileModelIndex.put(fileModelId, modelName); Set apsModelIds = apsModelIndex.getKeys(modelName); + if (apsModelIds == null || apsModelIds.isEmpty()) { + this.logger.debug("The model file '{}' has no corresponding model in APS; treating as new", modelFile.getName()); + continue; + } + if (apsModelIds.size() > 1) throw new IllegalStateException("An APS " + modelDirectory.getName() + " name was used multiple times: " + modelName); - if (renameFiles && !apsModelIds.isEmpty()) { + if (renameFiles) { Long apsModelId = apsModelIds.iterator().next(); if (apsModelId != fileModelId) { this.logger.debug("Renaming form model from '" + modelFile.getName() + "' to match APS form ID: " + apsModelId); File newModelFile = new File(modelFile.getParentFile(), modelName + "-" + apsModelId + "." + modelExt); modelFile.renameTo(newModelFile); } else { - this.logger.debug("The APS model '{}' and corresponding model file have matching IDs", modelName); + this.logger.debug("The APS model '{}' and corresponding model file '{}' have matching IDs", modelName, modelFile.getName()); } } } return fileModelIndex; } - - protected void translateModels(String modelsDirectoryName, ApsFileTranslator translator) throws IOException { - this.translateModels(modelsDirectoryName, Collections.singletonMap("json", translator)); - } - - protected void translateModels(String modelsDirectoryName, Map translators) throws IOException { - File modelsDirectory = new File(this.appDirectory, modelsDirectoryName); - for (File modelFile : modelsDirectory.listFiles()) { - Matcher matcher = this.filenamePattern.matcher(modelFile.getName()); - if (!matcher.find()) { - this.logger.warn("The '{}' folder has a file with an unexpected filename format; skipping: {}", modelsDirectoryName, modelFile); - continue; - } - - String ext = matcher.group(3); - ApsFileTranslator translator = translators.get(ext.toLowerCase()); - if (translator == null) { - this.logger.warn("The '{}' folder has a file with an unexpected extension; skipping: {}", modelsDirectoryName, modelFile); - continue; - } - - String modelName = matcher.group(1); - Long modelId = new Long(matcher.group(2)); - this.translate(translator, modelFile, modelName, modelId); - } - } - - private void translate(ApsFileTranslator translator, File modelFile, String modelName, Long modelId) throws IOException { - try { - translator.translateFile(modelFile, modelName, modelId); - } catch (IllegalArgumentException | IllegalStateException ie) { - if (this.failOnIntegrityViolation) { - throw ie; - } else { - this.logger.warn(ie.getMessage()); - } - } - } } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFileTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFileTranslator.java new file mode 100644 index 0000000..f542e56 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFileTranslator.java @@ -0,0 +1,17 @@ +package com.inteligr8.maven.aps.modeling.translator; + +import java.io.File; +import java.io.IOException; + +import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer; + +public interface ApsFileTranslator extends ApsFileTransformer { + + @Override + default void transformFile(File file, String modelName, Long modelId) throws IOException { + this.translateFile(file, modelName, modelId); + } + + void translateFile(File file, String modelName, Long modelId) throws IOException; + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsFormJsonTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFormJsonTranslator.java similarity index 87% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsFormJsonTranslator.java rename to src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFormJsonTranslator.java index 1d9228a..eb46762 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsFormJsonTranslator.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsFormJsonTranslator.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.translator; import java.io.File; import java.io.IOException; @@ -8,6 +8,8 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.inteligr8.maven.aps.modeling.util.Index; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; public class ApsFormJsonTranslator implements ApsFileTranslator { @@ -44,7 +46,7 @@ public class ApsFormJsonTranslator implements ApsFileTranslator { } if (changed) - ModelUtil.getInstance().writeJson(jsonDescriptor, file); + ModelUtil.getInstance().writeJson(jsonDescriptor, file, false); } } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessBpmnTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessBpmnTranslator.java similarity index 87% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessBpmnTranslator.java rename to src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessBpmnTranslator.java index 61966ca..456f74a 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessBpmnTranslator.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessBpmnTranslator.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.translator; import java.io.File; import java.io.IOException; @@ -16,13 +16,15 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Attr; -import org.w3c.dom.CDATASection; +import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.inteligr8.alfresco.activiti.model.Group; +import com.inteligr8.maven.aps.modeling.util.Index; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; public class ApsProcessBpmnTranslator implements ApsFileTranslator { @@ -72,7 +74,7 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { changed = this.translateTaskForm(formKeyElement) || changed; } - NodeList subprocessElements = ModelUtil.getInstance().xpath(definitionsElement, "//subProcess"); + NodeList subprocessElements = ModelUtil.getInstance().xpath(definitionsElement, "//tns:subProcess"); for (int n = 0; n < subprocessElements.getLength(); n++) { Element subprocessElement = (Element)subprocessElements.item(n); changed = this.translateSubprocess(subprocessElement) || changed; @@ -84,9 +86,9 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { changed = this.translateConditionDefinition(conditionDefinitionElement) || changed; } - NodeList conditionExpressions = ModelUtil.getInstance().xpath(definitionsElement, "//conditionExpression/text()"); + NodeList conditionExpressions = ModelUtil.getInstance().xpath(definitionsElement, "//tns:conditionExpression/text()"); for (int n = 0; n < conditionExpressions.getLength(); n++) { - CDATASection conditionExpression = (CDATASection)conditionExpressions.item(n); + CharacterData conditionExpression = (CharacterData)conditionExpressions.item(n); changed = this.translateConditionExpression(conditionExpression) || changed; } @@ -100,10 +102,10 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { private boolean translateRootElement(Element definitionsElement, Long apsProcessId) { boolean changed = false; - changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelId", apsProcessId) || changed; + if (apsProcessId != null) + changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelId", apsProcessId) || changed; changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion") || changed; - changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "exportDateTime") || changed; - changed = this.removeAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelLastUpdated") || changed; + //changed = this.setAttributeIfSet(definitionsElement, NAMESPACE_ACTIVITI_MODELER, "modelVersion", "0") || changed; return changed; } @@ -146,7 +148,7 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { if (apsOrgId != orgId) { this.logger.debug("The organization '{}' exists in APS with ID {}; changing model", groupName, apsOrgId); groupNameElement = (Element)groupNameElement.getOwnerDocument().renameNode(groupNameElement, - groupNameElement.getNamespaceURI(), "group-info-name-" + apsOrgId); + groupNameElement.getNamespaceURI(), "modeler:group-info-name-" + apsOrgId); changed = true; } else { this.logger.trace("The organization '{}' ID does not change; leaving unchanged", groupName); @@ -198,17 +200,17 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { this.logger.trace("The form '{}' key does not change; leaving unchanged", formRefName); } - CDATAFlexSection formRefIdData = (CDATAFlexSection)ModelUtil.getInstance().xpath( + CharacterData formRefIdData = (CharacterData)ModelUtil.getInstance().xpath( taskElement, "tns:extensionElements/modeler:form-reference-id", ModelUtil.CDATA_FLEX); if (formRefIdData == null) throw new IllegalStateException("A form was detected in the task, but no form reference ID was found: " + taskElement.getAttribute("id")); - long formRefId = Long.parseLong(formRefIdData.getValue()); + long formRefId = Long.parseLong(formRefIdData.getData()); if (apsFormId != formRefId) { this.logger.debug("The form '{}' reference exists in APS with ID {}; changing model", formRefName, apsFormId); - formRefIdData.setValue(apsFormId.toString()); + formRefIdData.setData(apsFormId.toString()); changed = true; } else { this.logger.trace("The form '{}' reference ID does not change; leaving unchanged", formRefName); @@ -242,14 +244,14 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { this.logger.trace("Found ID {} for the subprocess '{}' in the APS Process BPMN model", apsProcessId, subprocessName); - CDATAFlexSection subprocessIdData = (CDATAFlexSection)ModelUtil.getInstance().xpath( + CharacterData subprocessIdData = (CharacterData)ModelUtil.getInstance().xpath( subprocessElement, "tns:extensionElements/modeler:subprocess-id", ModelUtil.CDATA_FLEX); - long subprocessId = Long.parseLong(subprocessIdData.getValue()); + long subprocessId = Long.parseLong(subprocessIdData.getData()); if (apsProcessId != subprocessId) { this.logger.debug("The process '{}' exists in APS with ID {}; changing model", subprocessName, apsProcessId); - subprocessIdData.setValue(apsProcessId.toString()); + subprocessIdData.setData(apsProcessId.toString()); changed = true; } else { this.logger.trace("The process '{}' ID does not change; leaving unchanged", subprocessName); @@ -282,28 +284,42 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator { return changed; } - private boolean translateConditionExpression(CDATASection conditionExpressionCdata) throws XPathExpressionException { + private boolean translateConditionExpression(CharacterData conditionExpressionCdata) throws XPathExpressionException { this.logger.trace("Translating condition expression for outcome form elements: {}", conditionExpressionCdata.getParentNode().getParentNode().getAttributes().getNamedItem("id")); boolean changed = false; String conditionExpression = conditionExpressionCdata.getData(); - Pattern formOutcomeInExpressionPattern = Pattern.compile("[^A-Za-z0-9]form([0-9]+)outcome[^A-Za-z0-9]"); + StringBuilder newce = new StringBuilder(); + int pos = 0; + + Pattern formOutcomeInExpressionPattern = Pattern.compile("form([0-9]+)outcome"); Matcher matcher = formOutcomeInExpressionPattern.matcher(conditionExpression); while (matcher.find()) { + newce.append(conditionExpression.substring(pos, matcher.start(1))); + long formId = Long.parseLong(matcher.group(1)); String formName = this.fileFormIndex.getValue(formId); if (formName == null) { this.logger.warn("The form ID '{}' does not exist in the form model files; ignoring", formId); + newce.append(formId); } else { Long apsFormId = this.apsFormIndex.getFirstKey(formName); if (apsFormId == null) { this.logger.warn("The form '{}' does not exist in APS; leaving unchanged", formName); + newce.append(formId); } else { - conditionExpressionCdata.setData(apsFormId.toString()); + newce.append(apsFormId); changed = true; } } + + pos = matcher.end(1); } + + newce.append(conditionExpression.substring(pos)); + + if (changed) + conditionExpressionCdata.setData(newce.toString()); return changed; } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessJsonTranslator.java b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessJsonTranslator.java similarity index 87% rename from src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessJsonTranslator.java rename to src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessJsonTranslator.java index f61b020..9c0175e 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ApsProcessJsonTranslator.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/translator/ApsProcessJsonTranslator.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.translator; import java.io.File; import java.io.IOException; @@ -11,6 +11,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.inteligr8.alfresco.activiti.model.Group; +import com.inteligr8.maven.aps.modeling.util.Index; +import com.inteligr8.maven.aps.modeling.util.ModelUtil; public class ApsProcessJsonTranslator implements ApsFileTranslator { @@ -43,17 +45,32 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator { changed = this.translateSubprocesses(jsonDescriptor) || changed; if (changed) - ModelUtil.getInstance().writeJson(jsonDescriptor, file); + ModelUtil.getInstance().writeJson(jsonDescriptor, file, false); } private boolean translateResourceId(ObjectNode jsonDescriptor, Long apsProcessId) { + if (apsProcessId == null) { + this.logger.trace("The process is not in APS; treating as new"); + return false; + } + + ObjectNode resourceIdParentJson = jsonDescriptor; + JsonNode jsonResourceId = jsonDescriptor.get("resourceId"); + if (jsonResourceId == null || jsonResourceId.isNull()) { + JsonNode jsonEditorJson = jsonDescriptor.get("editorJson"); + if (jsonEditorJson != null && jsonEditorJson.isObject()) { + resourceIdParentJson = (ObjectNode)jsonEditorJson; + jsonResourceId = jsonEditorJson.get("resourceId"); + } + } + if (jsonResourceId == null || jsonResourceId.isNull()) { this.logger.trace("The process 'resourceId' is not defined; leaving unchanged", apsProcessId); return false; } else if (jsonResourceId.asLong() != apsProcessId) { this.logger.debug("The process is found in APS with ID {}; changing 'resourceId' in descriptor", apsProcessId); - jsonDescriptor.put("resourceId", apsProcessId); + resourceIdParentJson.put("resourceId", apsProcessId); return true; } else { this.logger.trace("The process ID {} does not change; leaving unchanged", apsProcessId); @@ -113,8 +130,8 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator { for (JsonNode jsonChildShape : this.getChildShapes(jsonDescriptor)) { try { - JsonNode jsonConditionalSequenceFlow = (JsonNode)jsonChildShape.get("properties").get("conditionalsequenceflow"); - changed = this.translateConditionalSequenceFlow(jsonConditionalSequenceFlow) || changed; + JsonNode jsonConditionalSequenceFlow = (JsonNode)jsonChildShape.get("properties").get("conditionsequenceflow"); + changed = this.translateConditionSequenceFlow(jsonConditionalSequenceFlow) || changed; JsonNode jsonFormRef = (JsonNode)jsonChildShape.get("properties").get("formreference"); changed = this.translateReference(jsonFormRef, this.apsFormIndex) || changed; @@ -126,7 +143,7 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator { return changed; } - private boolean translateConditionalSequenceFlow(JsonNode jsonConditionalSequenceFlow) { + private boolean translateConditionSequenceFlow(JsonNode jsonConditionalSequenceFlow) { if (jsonConditionalSequenceFlow == null || !jsonConditionalSequenceFlow.isObject()) return false; diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelArrayNodeSorter.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelArrayNodeSorter.java new file mode 100644 index 0000000..11c49c3 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelArrayNodeSorter.java @@ -0,0 +1,22 @@ +package com.inteligr8.maven.aps.modeling.util; + +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class ApsModelArrayNodeSorter extends ArrayNodeObjectSorter { + + private static ApsModelArrayNodeSorter INSTANCE = new ApsModelArrayNodeSorter(); + + public static ApsModelArrayNodeSorter getInstance() { + return INSTANCE; + } + + + + protected ApsModelArrayNodeSorter() { + } + + public boolean sort(ArrayNode arrayNode, String jsonObjectFieldName) { + return this.sort(arrayNode, new ObjectNodeComparator(jsonObjectFieldName)); + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelListMapSorter.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelListMapSorter.java new file mode 100644 index 0000000..212a048 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ApsModelListMapSorter.java @@ -0,0 +1,23 @@ +package com.inteligr8.maven.aps.modeling.util; + +import java.util.List; +import java.util.Map; + +public class ApsModelListMapSorter extends ListMapSorter { + + private static ApsModelListMapSorter INSTANCE = new ApsModelListMapSorter(); + + public static ApsModelListMapSorter getInstance() { + return INSTANCE; + } + + + + protected ApsModelListMapSorter() { + } + + public boolean sort(List> jsonArrayAsMap, String jsonObjectFieldName) { + return this.sort(jsonArrayAsMap, new MapComparator(jsonObjectFieldName)); + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/ArrayNodeObjectSorter.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ArrayNodeObjectSorter.java new file mode 100644 index 0000000..b68297e --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ArrayNodeObjectSorter.java @@ -0,0 +1,82 @@ +package com.inteligr8.maven.aps.modeling.util; + +import java.util.Comparator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class ArrayNodeObjectSorter { + + private static ArrayNodeObjectSorter INSTANCE = new ArrayNodeObjectSorter(); + + public static ArrayNodeObjectSorter getInstance() { + return INSTANCE; + } + + + + protected ArrayNodeObjectSorter() { + } + + public boolean sort(ArrayNode arrayNode, Comparator comparator) { + if (arrayNode.isEmpty()) + return false; + + boolean sorted = false; + boolean changed = false; + int index = 0; + JsonNode node = arrayNode.get(index); + + // simple bubble sort + while (!sorted) { + sorted = true; + int nextIndex = index + 1; + + // forward bubble + while (nextIndex < arrayNode.size()) { + JsonNode nextNode = arrayNode.get(nextIndex); + int c = comparator.compare(node, nextNode); + + if (c == 0) { + node = nextNode; + } else if (c < 0) { + node = nextNode; + } else { + arrayNode.insert(index, arrayNode.remove(nextIndex)); + sorted = false; + changed = true; + } + + index = nextIndex; + nextIndex = index + 1; + } + + if (sorted) + break; + sorted = true; + nextIndex = index - 1; + + // reverse bubble + while (nextIndex >= 0) { + JsonNode nextNode = arrayNode.get(nextIndex); + int c = comparator.compare(nextNode, node); + + if (c == 0) { + node = nextNode; + } else if (c < 0) { + node = nextNode; + } else { + arrayNode.insert(nextIndex, arrayNode.remove(index)); + sorted = false; + changed = true; + } + + index = nextIndex; + nextIndex = index - 1; + } + } + + return changed; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/Index.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/Index.java similarity index 93% rename from src/main/java/com/inteligr8/maven/aps/modeling/Index.java rename to src/main/java/com/inteligr8/maven/aps/modeling/util/Index.java index 6eb7f79..631e37f 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/Index.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/Index.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.util; import java.util.HashMap; import java.util.HashSet; @@ -33,6 +33,14 @@ public class Index { return this.forwardMap.get(key); } + public Set keySet() { + return this.forwardMap.keySet(); + } + + public Set valueSet() { + return this.reverseMap.keySet(); + } + public Set getKeys(V value) { return this.reverseMap.get(value); } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/ListMapSorter.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ListMapSorter.java new file mode 100644 index 0000000..ae8b909 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ListMapSorter.java @@ -0,0 +1,70 @@ +package com.inteligr8.maven.aps.modeling.util; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class ListMapSorter { + + public boolean sort(List> listOfMaps, Comparator> comparator) { + if (listOfMaps.isEmpty()) + return false; + + boolean sorted = false; + boolean changed = false; + int index = 0; + Map map = listOfMaps.get(index); + + // simple bubble sort + while (!sorted) { + sorted = true; + int nextIndex = index + 1; + + // forward bubble + while (nextIndex < listOfMaps.size()) { + Map nextMap = listOfMaps.get(nextIndex); + int c = comparator.compare(map, nextMap); + + if (c == 0) { + map = nextMap; + } else if (c < 0) { + map = nextMap; + } else { + listOfMaps.add(index, listOfMaps.remove(nextIndex)); + sorted = false; + changed = true; + } + + index = nextIndex; + nextIndex = index + 1; + } + + if (sorted) + break; + sorted = true; + nextIndex = index - 1; + + // reverse bubble + while (nextIndex >= 0) { + Map nextMap = listOfMaps.get(nextIndex); + int c = comparator.compare(nextMap, map); + + if (c == 0) { + map = nextMap; + } else if (c < 0) { + map = nextMap; + } else { + listOfMaps.add(nextIndex, listOfMaps.remove(index)); + sorted = false; + changed = true; + } + + index = nextIndex; + nextIndex = index - 1; + } + } + + return changed; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/MapComparator.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/MapComparator.java new file mode 100644 index 0000000..b5ebec1 --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/MapComparator.java @@ -0,0 +1,49 @@ +package com.inteligr8.maven.aps.modeling.util; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +public class MapComparator implements Comparator> { + + //private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class); + private final List keys; + + public MapComparator(@SuppressWarnings("unchecked") K... keys) { + this(Arrays.asList(keys)); + } + + public MapComparator(List keys) { + this.keys = keys; + } + + @Override + public int compare(Map m1, Map m2) { + for (K key : keys) { + V value1 = m1.get(key); + V value2 = m2.get(key); + + if (value1 == null && value2 == null) + continue; + + if (value1 == null) + return 1; + if (value2 == null) + return -1; + if (!value1.getClass().equals(value2.getClass())) + return 0; + + if (value1 instanceof Boolean) { + return Boolean.compare((Boolean)value1, (Boolean)value2); + } if (value1 instanceof Number) { + return Double.compare(((Number)value1).doubleValue(), ((Number)value2).doubleValue()); + } if (value1 instanceof String) { + return ((String)value1).compareTo((String)value2); + } + } + + return 0; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/ModelUtil.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ModelUtil.java similarity index 71% rename from src/main/java/com/inteligr8/maven/aps/modeling/ModelUtil.java rename to src/main/java/com/inteligr8/maven/aps/modeling/util/ModelUtil.java index 2e1f7b6..65f7739 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/ModelUtil.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ModelUtil.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.util; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -8,6 +8,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.List; +import java.util.Map; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; @@ -25,7 +27,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import org.w3c.dom.CDATASection; +import org.w3c.dom.CharacterData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -36,10 +38,10 @@ 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.inteligr8.maven.aps.modeling.xml.DomNamespaceContext; public class ModelUtil { - public final static QName CDATA = QName.valueOf("inteligr8:xml:cdata"); public final static QName CDATA_FLEX = QName.valueOf("inteligr8:xml:cdata_or_element"); private final static ModelUtil INSTANCE = new ModelUtil(); @@ -50,6 +52,7 @@ public class ModelUtil { private final ObjectMapper om = new ObjectMapper(); + private final ObjectMapper omsorted = new ObjectMapper(); private final DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); private final DocumentBuilder docBuilder; private final XPathFactory xpathfactory = XPathFactory.newInstance(); @@ -77,41 +80,28 @@ public class ModelUtil { // makes Git handling a whole lot better this.om.enable(SerializationFeature.INDENT_OUTPUT); - // makes Git handling better between environment nuances - this.om.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); - this.om.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); - } - - - - public void json(File inplaceFile) throws IOException { - JsonNode json; - - FileInputStream fistream = new FileInputStream(inplaceFile); - BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); - try { - json = this.readJson(bistream); - } finally { - bistream.close(); - } + this.omsorted.enable(SerializationFeature.INDENT_OUTPUT); - FileOutputStream fostream = new FileOutputStream(inplaceFile); - BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize); - try { - this.writeJson(json, bostream); - } finally { - bostream.close(); - } + // makes Git handling better between environment nuances + this.omsorted.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); + this.omsorted.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); } - public void json(File sourceFile, File targetFile) throws IOException { + + + public void json(File inplaceFile, boolean enableSorting) throws IOException { + JsonNode json = this.readJson(inplaceFile); + this.writeJson(json, inplaceFile, enableSorting); + } + + public void json(File sourceFile, File targetFile, boolean enableSorting) throws IOException { FileInputStream fistream = new FileInputStream(sourceFile); BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); try { FileOutputStream fostream = new FileOutputStream(targetFile); BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize); try { - this.json(bistream, bostream); + this.json(bistream, bostream, enableSorting); } finally { bostream.close(); } @@ -120,17 +110,17 @@ public class ModelUtil { } } - public void json(InputStream istream, OutputStream ostream) throws IOException { + public void json(InputStream istream, OutputStream ostream, boolean enableSorting) throws IOException { // FIXME stream it for lower memory/IO usage JsonNode json = this.readJson(istream); - this.writeJson(json, ostream); + this.writeJson(json, ostream, enableSorting); } public JsonNode readJson(File file) throws IOException { return this.readJson(file, JsonNode.class); } - public T readJson(File file, Class type) throws IOException { + public T readJson(File file, Class type) throws IOException { FileInputStream fistream = new FileInputStream(file); BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); try { @@ -140,26 +130,78 @@ public class ModelUtil { } } + public List readJsonAsList(File file) throws IOException { + FileInputStream fistream = new FileInputStream(file); + BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); + try { + return this.readJsonAsList(bistream); + } finally { + bistream.close(); + } + } + + public Map readJsonAsMap(File file) throws IOException { + FileInputStream fistream = new FileInputStream(file); + BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize); + try { + return this.readJsonAsMap(bistream); + } finally { + bistream.close(); + } + } + public JsonNode readJson(InputStream istream) throws IOException { return this.om.readTree(istream); } - public T readJson(InputStream istream, Class type) throws IOException { + public T readJson(InputStream istream, Class type) throws IOException { return this.om.readValue(istream, type); } - public void writeJson(JsonNode json, File file) throws IOException { + @SuppressWarnings("unchecked") + public List readJsonAsList(InputStream istream) throws IOException { + return this.om.readValue(istream, List.class); + } + + @SuppressWarnings("unchecked") + public Map readJsonAsMap(InputStream istream) throws IOException { + return this.om.readValue(istream, Map.class); + } + + public void writeJson(JsonNode json, File file, boolean enableSorting) throws IOException { FileOutputStream fostream = new FileOutputStream(file); BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize); try { - this.writeJson(json, bostream); + this.writeJson(json, bostream, enableSorting); } finally { bostream.close(); } } - public void writeJson(JsonNode json, OutputStream ostream) throws IOException { - this.om.writeValue(ostream, json); + public void writeJson(Map map, File file, boolean enableSorting) throws IOException { + FileOutputStream fostream = new FileOutputStream(file); + BufferedOutputStream bostream = new BufferedOutputStream(fostream, this.bufferSize); + try { + this.writeJson(map, bostream, enableSorting); + } finally { + bostream.close(); + } + } + + public void writeJson(JsonNode json, OutputStream ostream, boolean enableSorting) throws IOException { + if (enableSorting) { + this.omsorted.writeValue(ostream, json); + } else { + this.om.writeValue(ostream, json); + } + } + + public void writeJson(Map map, OutputStream ostream, boolean enableSorting) throws IOException { + if (enableSorting) { + this.omsorted.writeValue(ostream, map); + } else { + this.om.writeValue(ostream, map); + } } @@ -244,22 +286,23 @@ public class ModelUtil { XPath xpath = this.xpathfactory.newXPath(); xpath.setNamespaceContext(new DomNamespaceContext(node)); - boolean isFlex = returnType.equals(ModelUtil.CDATA_FLEX); - - if (returnType.equals(ModelUtil.CDATA) || isFlex) { + if (returnType.equals(ModelUtil.CDATA_FLEX)) { NodeList nodes = (NodeList)xpath.evaluate(xpathExpr, node, XPathConstants.NODESET); Node anode = this.getFirstCDataSectionOrLastElement(nodes); - if (anode instanceof CDATASection) { + if (anode instanceof CharacterData) { // nothing special } else if (anode instanceof Element) { Node anode2 = this.getFirstCDataSectionOrLastElement(anode.getChildNodes()); - if (anode2 != null) - return anode = anode2; + if (anode2 == null) + return null; + if (!(anode2 instanceof CharacterData)) + throw new IllegalStateException(); + anode = anode2; } else { return null; } - return isFlex ? new CDATAFlexSection(anode) : anode; + return anode; } else { return xpath.evaluate(xpathExpr, node, returnType); } @@ -270,8 +313,8 @@ public class ModelUtil { for (int n = 0; n < nodes.getLength(); n++) { Node anode = nodes.item(n); - if (anode.getNodeType() == Node.CDATA_SECTION_NODE) { - return (CDATASection)anode; + if (anode instanceof CharacterData) { + return (CharacterData)anode; } else if (anode.getNodeType() == Node.ELEMENT_NODE) { element = (Element)anode; } diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/util/ObjectNodeComparator.java b/src/main/java/com/inteligr8/maven/aps/modeling/util/ObjectNodeComparator.java new file mode 100644 index 0000000..729639c --- /dev/null +++ b/src/main/java/com/inteligr8/maven/aps/modeling/util/ObjectNodeComparator.java @@ -0,0 +1,58 @@ +package com.inteligr8.maven.aps.modeling.util; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ObjectNodeComparator implements Comparator { + + //private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class); + private final List objectFieldNames; + + public ObjectNodeComparator(String... objectFieldNames) { + this(Arrays.asList(objectFieldNames)); + } + + public ObjectNodeComparator(List objectFieldNames) { + this.objectFieldNames = objectFieldNames; + } + + @Override + public int compare(JsonNode o1, JsonNode o2) { + if (!o1.isObject()) + return -1; + if (!o2.isObject()) + return 1; + + for (String objectFieldName : objectFieldNames) { + JsonNode value1 = o1.get(objectFieldName); + JsonNode value2 = o2.get(objectFieldName); + + if (value1 == null && value2 == null) + continue; + + if (value1 == null) + return 1; + if (value2 == null) + return -1; + if (!value1.getNodeType().equals(value2.getNodeType())) + return 0; + + switch (value1.getNodeType()) { + case BOOLEAN: + return Boolean.compare(value1.asBoolean(), value2.asBoolean()); + case NUMBER: + return Double.compare(value1.asDouble(), value2.asDouble()); + case STRING: + return value1.asText().compareTo(value2.asText()); + default: + return 0; + } + } + + return 0; + } + +} diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/DomNamespaceContext.java b/src/main/java/com/inteligr8/maven/aps/modeling/xml/DomNamespaceContext.java similarity index 97% rename from src/main/java/com/inteligr8/maven/aps/modeling/DomNamespaceContext.java rename to src/main/java/com/inteligr8/maven/aps/modeling/xml/DomNamespaceContext.java index 09e0aef..4c9872d 100644 --- a/src/main/java/com/inteligr8/maven/aps/modeling/DomNamespaceContext.java +++ b/src/main/java/com/inteligr8/maven/aps/modeling/xml/DomNamespaceContext.java @@ -1,4 +1,4 @@ -package com.inteligr8.maven.aps.modeling; +package com.inteligr8.maven.aps.modeling.xml; import java.util.Collections; import java.util.Iterator; diff --git a/src/main/resources/bpmn.xslt b/src/main/resources/bpmn.xslt index 9659319..05b3f84 100644 --- a/src/main/resources/bpmn.xslt +++ b/src/main/resources/bpmn.xslt @@ -1,9 +1,14 @@ - + + + <![CDATA[ + + ]]> +