13 Commits

47 changed files with 1531 additions and 661 deletions

41
pom.xml
View File

@@ -7,7 +7,7 @@
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-model-maven-plugin</artifactId>
<version>1.0.0</version>
<version>1.2.0</version>
<packaging>maven-plugin</packaging>
<name>A Maven plugin for Alfresco Process Services model portability</name>
@@ -34,36 +34,27 @@
<maven.compiler.target>1.8</maven.compiler.target>
<maven.version>3.6.3</maven.version>
<jersey.version>2.34</jersey.version>
<jersey.version>2.35</jersey.version>
</properties>
<dependencies>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-api</artifactId>
<version>1.2.0</version>
<version>2.0.0</version>
<classifier>aps1</classifier>
</dependency>
<dependency>
<groupId>com.inteligr8.alfresco</groupId>
<artifactId>aps-public-rest-client</artifactId>
<version>2.0.0</version>
<classifier>jersey</classifier>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.ext</groupId>
<artifactId>jersey-proxy-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-multipart</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
@@ -161,8 +152,13 @@
<localRepositoryPath>${project.build.directory}/it-repo</localRepositoryPath>
<mavenHome>${env.MAVEN_HOME}</mavenHome>
<debug>true</debug>
<skipInvocation>${skipTests}</skipInvocation>
<properties>
<project.main.basedir>${basedir}</project.main.basedir>
<aps-model.baseUrl>${aps-model.baseUrl}</aps-model.baseUrl>
<aps-model.authType>${aps-model.authType}</aps-model.authType>
<aps-model.basicAuth.mavenServerId>${aps-model.basicAuth.mavenServerId}</aps-model.basicAuth.mavenServerId>
<aps-model.appName>${aps-model.appName}</aps-model.appName>
</properties>
</configuration>
<executions>
@@ -184,6 +180,7 @@
<goals><goal>jar</goal></goals>
<configuration>
<show>public</show>
<skip>true</skip>
</configuration>
</execution>
</executions>
@@ -223,6 +220,14 @@
</build>
</profile>
</profiles>
<repositories>
<repository>
<id>inteligr8-releases</id>
<name>Inteligr8 Releases</name>
<url>https://repos.inteligr8.com/nexus/repository/inteligr8-public</url>
</repository>
</repositories>
<distributionManagement>
<repository>

View File

@@ -12,10 +12,6 @@
<name>Deploy App Plugin Tests</name>
<properties>
<aps.app>FORMS Core</aps.app>
</properties>
<build>
<plugins>
<plugin>
@@ -24,14 +20,46 @@
<version>@pom.version@</version>
<executions>
<execution>
<id>download-app</id>
<phase>validate</phase>
<id>download-unpack-app</id>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<reformat>true</reformat>
<normalize>true</normalize>
</configuration>
</execution>
<execution>
<id>translate-app</id>
<phase>compile</phase>
<goals>
<goal>translate-app</goal>
</goals>
<configuration>
<finalDirectory>${project.build.directory}/aps-dev</finalDirectory>
</configuration>
</execution>
<execution>
<id>pack-app</id>
<phase>package</phase>
<goals>
<goal>pack-app</goal>
</goals>
<configuration>
<unzipDirectory>${project.build.directory}/aps-dev</unzipDirectory>
<zipDirectory>${project.build.directory}/aps-test</zipDirectory>
</configuration>
</execution>
<execution>
<id>deploy-app</id>
<phase>package</phase>
<goals>
<goal>deploy-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<zipDirectory>${project.build.directory}/aps-test</zipDirectory>
</configuration>
</execution>
</executions>

View File

@@ -12,10 +12,6 @@
<name>Download App Plugin Tests</name>
<properties>
<aps.app>FORMS Core</aps.app>
</properties>
<build>
<plugins>
<plugin>
@@ -25,23 +21,19 @@
<executions>
<execution>
<id>download-app</id>
<phase>validate</phase>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
</configuration>
</execution>
<execution>
<id>download-app-other</id>
<phase>validate</phase>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<targetDirectory>${basedir}/src/main/app</targetDirectory>
<zipDirectory>${basedir}/src/main/aps</zipDirectory>
</configuration>
</execution>
</executions>
@@ -57,8 +49,8 @@
<rules>
<requireFilesExist>
<files>
<file>${project.build.directory}/app/${aps.app}.zip</file>
<file>${basedir}/src/main/app/${aps.app}.zip</file>
<file>${project.build.directory}/aps/${aps-model.appName}.zip</file>
<file>${basedir}/src/main/aps/${aps-model.appName}.zip</file>
</files>
</requireFilesExist>
</rules>

View File

@@ -12,10 +12,6 @@
<name>Pack App Plugin Tests</name>
<properties>
<aps.app>FORMS Core</aps.app>
</properties>
<build>
<plugins>
<plugin>
@@ -25,24 +21,23 @@
<executions>
<execution>
<id>download-unpack-app</id>
<phase>validate</phase>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<normalize>true</normalize>
</configuration>
</execution>
<execution>
<id>pack-app</id>
<phase>validate</phase>
<phase>package</phase>
<goals>
<goal>pack-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<targetDirectory>${basedir}/src/main/app</targetDirectory>
<zipDirectory>${basedir}</zipDirectory>
</configuration>
</execution>
</executions>
@@ -58,7 +53,7 @@
<rules>
<requireFilesExist>
<files>
<file>${basedir}/src/main/app/${aps.app}.zip</file>
<file>${basedir}/${aps-model.appName}.zip</file>
</files>
</requireFilesExist>
</rules>

View File

@@ -12,10 +12,6 @@
<name>Translate App Plugin Tests</name>
<properties>
<aps.app>FORMS Core</aps.app>
</properties>
<build>
<plugins>
<plugin>
@@ -25,24 +21,24 @@
<executions>
<execution>
<id>download-unpack-app</id>
<phase>validate</phase>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<normalize>true</normalize>
<diffFriendly>true</diffFriendly>
</configuration>
</execution>
<execution>
<id>translate-app</id>
<phase>validate</phase>
<phase>compile</phase>
<goals>
<goal>translate-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<targetDirectory>${basedir}/src/main/app</targetDirectory>
<finalDirectory>${basedir}/src/main/aps/app</finalDirectory>
</configuration>
</execution>
</executions>
@@ -58,7 +54,7 @@
<rules>
<requireFilesExist>
<files>
<file>${basedir}/src/main/app/${aps.app}/${aps.app}.json</file>
<file>${basedir}/src/main/aps/app/${aps-model.appName}/${aps-model.appName}.json</file>
</files>
</requireFilesExist>
</rules>

View File

@@ -12,10 +12,6 @@
<name>Unpack App Plugin Tests</name>
<properties>
<aps.app>FORMS Core</aps.app>
</properties>
<build>
<plugins>
<plugin>
@@ -25,24 +21,20 @@
<executions>
<execution>
<id>download-unpack-app</id>
<phase>validate</phase>
<phase>generate-sources</phase>
<goals>
<goal>download-app</goal>
<goal>unpack-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
</configuration>
</execution>
<execution>
<id>unpack-app</id>
<phase>validate</phase>
<phase>process-sources</phase>
<goals>
<goal>unpack-app</goal>
</goals>
<configuration>
<apsAppName>${aps.app}</apsAppName>
<targetDirectory>${basedir}/src/main/app</targetDirectory>
<unzipDirectory>${basedir}/src/main/aps/app</unzipDirectory>
</configuration>
</execution>
</executions>
@@ -58,8 +50,8 @@
<rules>
<requireFilesExist>
<files>
<file>${project.build.directory}/app/${aps.app}/${aps.app}.json</file>
<file>${basedir}/src/main/app/${aps.app}/${aps.app}.json</file>
<file>${project.build.directory}/aps/app/${aps-model.appName}/${aps-model.appName}.json</file>
<file>${basedir}/src/main/aps/app/${aps-model.appName}/${aps-model.appName}.json</file>
</files>
</requireFilesExist>
</rules>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
package com.inteligr8.maven.aps.modeling.crawler;
public interface ApsAppCrawlable {
ApsFileTransformer getAppJsonTransformer();
ApsFileTransformer getProcessJsonTransformer();
ApsFileTransformer getProcessBpmnTransformer();
ApsFileTransformer getFormJsonTransformer();
}

View File

@@ -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<String, ApsFileTransformer> 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<String, ApsFileTransformer> 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());
}
}
}
}

View File

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

View File

@@ -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.ApsClientJerseyConfiguration;
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 ApsClientJerseyConfiguration getApsClientConfiguration() {
this.getLog().debug("Configuring APS to URL: " + this.activitiAppBaseUrl);
ApsClientJerseyConfiguration config = new ApsClientJerseyConfiguration();
config.setBaseUrl(this.activitiAppBaseUrl);
switch (this.activitiAppAuthType.toUpperCase()) {
case "BASIC":
this.getLog().info("Configuring APS with BASIC authentication");
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) {
ApsClientJerseyConfiguration config = this.getApsClientConfiguration();
ApsClientJerseyImpl apsClient = new ApsClientJerseyImpl(config);
this.api = new ApsPublicRestApiJerseyImpl(apsClient);
}
return this.api;
}
}

View File

@@ -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<Tenant> tenants = this.getApsApi().getAdminApi().getTenants();
if (tenants == null || tenants.isEmpty())
return null;
return tenants.iterator().next().getId();
}
protected Map<String, Long> findAppNameIds() {
ApsPublicRestApiJerseyImpl api = this.getApsApi();
Map<String, Long> apps = new HashMap<>(16);
this.getLog().debug("Searching for all APS Apps");
ResultListDataRepresentation results = api.getModelsApi().get("everyone", null, ModelType.App.getId(), null);
this.getLog().debug("Found " + results.getTotal() + " APS Apps");
for (Datum datum : results.getData()) {
String name = (String)datum.getAdditionalProperties().get("name");
Number id = (Number)datum.getAdditionalProperties().get("id");
apps.put(name, id.longValue());
}
return apps;
}
protected Long findAppId() throws MojoExecutionException {
return this.findAppIdByName(this.apsAppName);
}
protected Long findAppIdByName(String appName) throws MojoExecutionException {
Map<String, Long> apps = this.findAppNameIds();
Long appId = apps.get(this.apsAppName);
if (appId == null)
throw new MojoExecutionException("The APS App '' could not be found; valid apps: " + apps.keySet());
return appId;
}
}

View File

@@ -0,0 +1,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;
}

View File

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

View File

@@ -1,61 +1,57 @@
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 java.text.ParseException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.component.annotations.Component;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import com.inteligr8.alfresco.activiti.ApsPublicRestApiJerseyImpl;
import com.inteligr8.alfresco.activiti.model.AppDefinitionUpdateResultRepresentation;
import com.inteligr8.alfresco.activiti.model.FileMultipartJersey;
@Mojo( name = "deploy-app", threadSafe = true )
@Component( role = org.apache.maven.plugin.Mojo.class )
public class DeployAppGoal extends ApsAppAccessibleGoal {
@Parameter( property = "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;
@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,35 +62,34 @@ 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 {
FileMultipartJersey multipart = FileMultipartJersey.from(appZip.getName(), bistream);
if (appId == null) {
if (this.publish) {
this.getLog().info("Uploading & publishing new APS App: " + this.apsAppName);
AppDefinitionUpdateResultRepresentation appDefUpdate = api.getAppDefinitionsJerseyApi().publishApp(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);
}
}
} catch (ParseException pe) {
throw new MojoExecutionException("The app ZIP could not be wrapped in a multipart", pe);
} finally {
bistream.close();
}

View File

@@ -0,0 +1,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;
}

View File

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

View File

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

View File

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

View File

@@ -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<String, Object> 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();

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
package com.inteligr8.maven.aps.modeling.normalizer;
import com.inteligr8.maven.aps.modeling.crawler.ApsFileTransformer;
public interface ApsFileNormalizer extends ApsFileTransformer {
}

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -9,33 +9,35 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsAppJsonTranslator implements ApsFileTranslator {
public class ApsAppJsonTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsAppJsonTranslator.class);
private Map<String, Group> apsOrgIndex;
private Index<Long, String> apsProcessIndex;
private Index<Long, String> fileProcessIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsProcessIndex;
private final Index<Long, String> fileProcessIndex;
public ApsAppJsonTranslator(
Map<String, Group> apsOrgIndex,
ApsPublicRestApi api,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsProcessIndex,
Index<Long, String> fileProcessIndex) {
super(api, apsOrgIndex);
this.apsOrgIndex = apsOrgIndex;
this.apsProcessIndex = apsProcessIndex;
this.fileProcessIndex = fileProcessIndex;
}
@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 +66,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")) {
@@ -74,23 +85,37 @@ public class ApsAppJsonTranslator implements ApsFileTranslator {
if (this.apsOrgIndex.containsKey(fileOrgName)) {
long fileOrgId = jsonGroup.get("id").asLong();
long apsOrgId = this.apsOrgIndex.get(fileOrgName).getId();
GroupLight apsOrg = this.apsOrgIndex.get(fileOrgName);
if (fileOrgId != apsOrgId) {
this.logger.debug("The organziation '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId);
jsonGroup.put("id", apsOrgId);
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; adding to APS", fileOrgName);
long apsGroupId = this.createOrganization(fileOrgName);
jsonGroup.put("id", apsGroupId);
changed = true;
} else if (fileOrgId != apsOrg.getId()) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrg.getId());
jsonGroup.put("id", apsOrg.getId());
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName);
}
if (jsonGroup.has("parentGroupId")) {
jsonGroup.put("parentGroupId", 0);
changed = true;
}
// if (jsonGroup.has("parentGroupId")) {
// jsonGroup.remove("parentGroupId");
// changed = true;
// }
} else {
this.logger.warn("The 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);
}
}

View File

@@ -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,110 +12,127 @@ 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.GroupLight;
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 Map<String, Group> apsOrgIndex;
private boolean indexesBuilt = false;
private Map<String, GroupLight> apsOrgIndex;
private Index<Long, String> apsFormIndex;
private Index<Long, String> apsProcessIndex;
private Index<Long, String> fileFormIndex;
private Index<Long, String> fileProcessIndex;
private Index<Long, String> fileSubprocessIndex;
public ApsAppTranslator(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 <K, V> void logLarge(String message, Map<K, V> map) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(message, map);
} else {
this.logger.debug(message, map.keySet());
}
}
private <K, V> void logLarge(String message, Index<K, V> index) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(message, index);
} else {
this.logger.debug(message, index.valueSet());
}
}
@Override
public ApsFileTransformer getAppJsonTransformer() {
if (!this.indexesBuilt)
throw new IllegalStateException("The indexes are never built");
ApsFileTranslator descriptorTranslator = new ApsAppJsonTranslator(
return new ApsAppJsonTranslator(
this.api,
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.api,
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.api,
this.apsProcessIndex,
this.apsOrgIndex,
this.apsFormIndex,
this.fileFormIndex);
Map<String, ApsFileTranslator> 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() {
@@ -124,17 +140,18 @@ public class ApsAppTranslator {
return (tenants == null || tenants.isEmpty()) ? null : tenants.iterator().next().getId();
}
protected Map<String, Group> buildApsGroupMap(long tenantId) {
List<Group> groups = this.api.getAdminApi().getGroups(tenantId, true, true);
protected Map<String, GroupLight> buildApsGroupMap(long tenantId) {
List<GroupLight> groups = this.api.getAdminApi().getGroups(tenantId, true, true);
Map<String, Group> map = new HashMap<>(groups.size());
for (Group group : groups)
Map<String, GroupLight> map = new HashMap<>(groups.size());
for (GroupLight group : groups)
map.put(group.getName(), group);
return map;
}
protected Index<Long, String> buildApsModelIndex(ModelType modelType) {
ResultListDataRepresentation results = this.api.getModelsApi().get(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<Long, String> map = new Index<>(results.getSize().intValue(), false);
@@ -143,11 +160,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 +176,50 @@ public class ApsAppTranslator {
}
protected Index<Long, String> buildFileIndex(File modelDirectory, Index<Long, String> 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<Long, String> 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<Long> 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<String, ApsFileTranslator> 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());
}
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,35 @@
package com.inteligr8.maven.aps.modeling.translator;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.model.GroupLight;
public class ApsOrganizationHandler {
private final Logger logger = LoggerFactory.getLogger(ApsOrganizationHandler.class);
private final ApsPublicRestApi api;
private final Map<String, GroupLight> apsOrgIndex;
public ApsOrganizationHandler(
ApsPublicRestApi api,
Map<String, GroupLight> apsOrgIndex) {
this.api = api;
this.apsOrgIndex = apsOrgIndex;
}
protected long createOrganization(String groupName) {
this.logger.info("Creating organization '{}' in APS", groupName);
GroupLight group = this.api.getAdminApi().createGroup(new Group()
.withName(groupName)
.withType(1L)); // an organization, not capability
this.apsOrgIndex.put(groupName, group);
return group.getId();
}
}

View File

@@ -1,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,34 +16,38 @@ 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.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsProcessBpmnTranslator implements ApsFileTranslator {
public class ApsProcessBpmnTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private static final String NAMESPACE_ACTIVITI = "http://activiti.org/bpmn";
private static final String NAMESPACE_ACTIVITI_MODELER = "http://activiti.com/modeler";
private final Logger logger = LoggerFactory.getLogger(ApsProcessBpmnTranslator.class);
private final Index<Long, String> apsIndex;
private final Map<String, Group> apsOrgIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
private final Index<Long, String> fileFormIndex;
public ApsProcessBpmnTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, Group> apsOrgIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex,
Index<Long, String> fileFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
this.fileFormIndex = fileFormIndex;
}
@@ -72,7 +76,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 +88,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 +104,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;
}
@@ -139,22 +143,26 @@ public class ApsProcessBpmnTranslator implements ApsFileTranslator {
String groupName = groupNameElement.getTextContent().trim();
this.logger.trace("Found '{}' candidate group in the APS Process BPMN model", groupName);
if (this.apsOrgIndex.containsKey(groupName)) {
long apsOrgId = this.apsOrgIndex.get(groupName).getId();
groupIdsList.add(apsOrgId);
if (apsOrgId != orgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing model", groupName, apsOrgId);
groupNameElement = (Element)groupNameElement.getOwnerDocument().renameNode(groupNameElement,
groupNameElement.getNamespaceURI(), "group-info-name-" + apsOrgId);
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", groupName);
}
GroupLight apsOrg = this.apsOrgIndex.get(groupName);
long apsOrgId = 0L;
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; creating it ...", groupName);
apsOrgId = this.createOrganization(groupName);
} else {
// FIXME automatically add the group?
this.logger.debug("The organization '{}' does not exist in APS; leaving unchanged", groupName);
groupIdsList.add(orgId);
apsOrgId = apsOrg.getId();
}
if (apsOrgId != orgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing model", groupName, apsOrgId);
groupNameElement = (Element)groupNameElement.getOwnerDocument().renameNode(groupNameElement,
groupNameElement.getNamespaceURI(), "modeler:group-info-name-" + apsOrgId);
groupIdsList.add(apsOrgId);
changed = true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", groupName);
groupIdsList.add(apsOrgId);
}
}
@@ -198,17 +206,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 +250,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 +290,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;
}

View File

@@ -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,19 +10,24 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.inteligr8.alfresco.activiti.model.Group;
import com.inteligr8.alfresco.activiti.ApsPublicRestApi;
import com.inteligr8.alfresco.activiti.model.GroupLight;
import com.inteligr8.maven.aps.modeling.util.Index;
import com.inteligr8.maven.aps.modeling.util.ModelUtil;
public class ApsProcessJsonTranslator implements ApsFileTranslator {
public class ApsProcessJsonTranslator extends ApsOrganizationHandler implements ApsFileTranslator {
private final Logger logger = LoggerFactory.getLogger(ApsProcessJsonTranslator.class);
private final Index<Long, String> apsIndex;
private final Map<String, Group> apsOrgIndex;
private final Map<String, GroupLight> apsOrgIndex;
private final Index<Long, String> apsFormIndex;
public ApsProcessJsonTranslator(
ApsPublicRestApi api,
Index<Long, String> apsProcessIndex,
Map<String, Group> apsOrgIndex,
Map<String, GroupLight> apsOrgIndex,
Index<Long, String> apsFormIndex) {
super(api, apsOrgIndex);
this.apsIndex = apsProcessIndex;
this.apsOrgIndex = apsOrgIndex;
this.apsFormIndex = apsFormIndex;
@@ -43,17 +48,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);
@@ -90,10 +110,16 @@ public class ApsProcessJsonTranslator implements ApsFileTranslator {
if (this.apsOrgIndex.containsKey(fileOrgName)) {
long fileOrgId = jsonCandidateGroup.get("id").asLong();
long apsOrgId = this.apsOrgIndex.get(fileOrgName).getId();
if (apsOrgId != fileOrgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrgId);
jsonCandidateGroup.put("id", apsOrgId);
GroupLight apsOrg = this.apsOrgIndex.get(fileOrgName);
if (apsOrg == null) {
this.logger.debug("The organization '{}' does not exist in APS; adding to APS", fileOrgName);
long apsGroupId = this.createOrganization(fileOrgName);
jsonCandidateGroup.put("id", apsGroupId);
return true;
} else if (apsOrg.getId() != fileOrgId) {
this.logger.debug("The organization '{}' exists in APS with ID {}; changing descriptor", fileOrgName, apsOrg.getId());
jsonCandidateGroup.put("id", apsOrg.getId());
return true;
} else {
this.logger.trace("The organization '{}' ID does not change; leaving unchanged", fileOrgName);
@@ -113,8 +139,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 +152,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;

View File

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

View File

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

View File

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

View File

@@ -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<K, V> {
return this.forwardMap.get(key);
}
public Set<K> keySet() {
return this.forwardMap.keySet();
}
public Set<V> valueSet() {
return this.reverseMap.keySet();
}
public Set<K> getKeys(V value) {
return this.reverseMap.get(value);
}

View File

@@ -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<K, V> {
public boolean sort(List<Map<K, V>> listOfMaps, Comparator<Map<K, V>> comparator) {
if (listOfMaps.isEmpty())
return false;
boolean sorted = false;
boolean changed = false;
int index = 0;
Map<K, V> map = listOfMaps.get(index);
// simple bubble sort
while (!sorted) {
sorted = true;
int nextIndex = index + 1;
// forward bubble
while (nextIndex < listOfMaps.size()) {
Map<K, V> 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<K, V> 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;
}
}

View File

@@ -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<K, V> implements Comparator<Map<K, V>> {
//private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class);
private final List<K> keys;
public MapComparator(@SuppressWarnings("unchecked") K... keys) {
this(Arrays.asList(keys));
}
public MapComparator(List<K> keys) {
this.keys = keys;
}
@Override
public int compare(Map<K, V> m1, Map<K, V> 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;
}
}

View File

@@ -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 extends JsonNode> T readJson(File file, Class<T> type) throws IOException {
public <T> T readJson(File file, Class<T> type) throws IOException {
FileInputStream fistream = new FileInputStream(file);
BufferedInputStream bistream = new BufferedInputStream(fistream, this.bufferSize);
try {
@@ -140,26 +130,78 @@ public class ModelUtil {
}
}
public <T> List<T> 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<String, Object> 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 extends JsonNode> T readJson(InputStream istream, Class<T> type) throws IOException {
public <T> T readJson(InputStream istream, Class<T> type) throws IOException {
return this.om.readValue(istream, type);
}
public void writeJson(JsonNode json, File file) throws IOException {
@SuppressWarnings("unchecked")
public <T> List<T> readJsonAsList(InputStream istream) throws IOException {
return this.om.readValue(istream, List.class);
}
@SuppressWarnings("unchecked")
public Map<String, Object> 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<String, Object> 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<String, Object> 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;
}

View File

@@ -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<JsonNode> {
//private final Logger logger = LoggerFactory.getLogger(ArrayOfObjectNodeComparator.class);
private final List<String> objectFieldNames;
public ObjectNodeComparator(String... objectFieldNames) {
this(Arrays.asList(objectFieldNames));
}
public ObjectNodeComparator(List<String> 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;
}
}

View File

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

View File

@@ -1,9 +1,14 @@
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:output indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:text disable-output-escaping="yes">&lt;![CDATA[</xsl:text>
<xsl:value-of select="." disable-output-escaping="yes" />
<xsl:text disable-output-escaping="yes">]]&gt;</xsl:text>
</xsl:template>
</xsl:stylesheet>