diff --git a/pom.xml b/pom.xml
index de6e588..729ac6f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -41,13 +41,13 @@
com.inteligr8.alfresco
aps-public-rest-api
- 2.0.0
+ 2.0.1
aps1
com.inteligr8.alfresco
aps-public-rest-client
- 2.0.0
+ 2.0.1
jersey
@@ -159,6 +159,9 @@
${aps-model.authType}
${aps-model.basicAuth.mavenServerId}
${aps-model.appName}
+ ${aps-model.share.editors}
+ ${aps-model.share.readers}
+ ${aps-model.share.app.editors}
diff --git a/src/it/share-models/pom.xml b/src/it/share-models/pom.xml
new file mode 100644
index 0000000..f3da8ef
--- /dev/null
+++ b/src/it/share-models/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ 4.0.0
+
+ com.inteligr8.alfresco
+ aps-model-maven-plugin-aps-info
+ @pom.version@
+ pom
+
+ APS Share Models Plugin Tests
+
+
+
+
+ ${project.groupId}
+ aps-model-maven-plugin
+ @pom.version@
+
+
+ share-models
+ validate
+
+ share-models
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java
new file mode 100644
index 0000000..e06d788
--- /dev/null
+++ b/src/main/java/com/inteligr8/maven/aps/modeling/goal/ApsShareGoal.java
@@ -0,0 +1,203 @@
+package com.inteligr8.maven.aps.modeling.goal;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.codehaus.plexus.component.annotations.Component;
+
+import com.inteligr8.alfresco.activiti.api.ModelsApi;
+import com.inteligr8.alfresco.activiti.model.Datum;
+import com.inteligr8.alfresco.activiti.model.GroupLight;
+import com.inteligr8.alfresco.activiti.model.PermissionLevel;
+import com.inteligr8.alfresco.activiti.model.PermissionLight;
+import com.inteligr8.alfresco.activiti.model.ResultList;
+import com.inteligr8.alfresco.activiti.model.ResultListDataRepresentation;
+import com.inteligr8.alfresco.activiti.model.ShareInfoRequest;
+import com.inteligr8.alfresco.activiti.model.SharePermission;
+import com.inteligr8.alfresco.activiti.model.Tenant;
+import com.inteligr8.maven.aps.modeling.util.Index;
+
+@Mojo( name = "share-models", threadSafe = true )
+@Component( role = org.apache.maven.plugin.Mojo.class )
+public class ApsShareGoal extends ApsAddressibleGoal {
+
+ @Parameter( property = "aps-model.share.readers" )
+ protected String readers;
+
+ @Parameter( property = "aps-model.share.editors" )
+ protected String editors;
+
+ @Parameter( property = "aps-model.share.app.readers" )
+ protected String appReaders;
+
+ @Parameter( property = "aps-model.share.app.editors" )
+ protected String appEditors;
+
+ @Parameter( property = "aps-model.share.process.readers" )
+ protected String processReaders;
+
+ @Parameter( property = "aps-model.share.process.editors" )
+ protected String processEditors;
+
+ @Parameter( property = "aps-model.share.form.readers" )
+ protected String formReaders;
+
+ @Parameter( property = "aps-model.share.form.editors" )
+ protected String formEditors;
+
+ @Parameter( property = "aps-model.share.doRevoke", defaultValue = "false" )
+ protected boolean doRevoke = false;
+
+ protected Set appReaderSet;
+ protected Set appEditorSet;
+ protected Set processReaderSet;
+ protected Set processEditorSet;
+ protected Set formReaderSet;
+ protected Set formEditorSet;
+
+ private Index identityIndex = new Index<>(128, true);
+
+ @Override
+ public void executeEnabled() throws MojoExecutionException, MojoFailureException {
+ this.getLog().info("editors: " + this.editors);
+ this.normalizeParameters();
+ this.buildIdentityIndex();
+
+ if (!this.appReaderSet.isEmpty() || !this.appEditorSet.isEmpty())
+ this.shareModels(ModelsApi.ModelType.App, this.appReaderSet, this.appEditorSet);
+
+ if (!this.processReaderSet.isEmpty() || !this.processEditorSet.isEmpty())
+ this.shareModels(ModelsApi.ModelType.Process, this.processReaderSet, this.processEditorSet);
+
+ if (!this.formReaderSet.isEmpty() || !this.formEditorSet.isEmpty())
+ this.shareModels(ModelsApi.ModelType.Form, this.formReaderSet, this.formEditorSet);
+ }
+
+ private void shareModels(ModelsApi.ModelType modelType, Set readers, Set editors) {
+ ResultListDataRepresentation models = this.getApsApi().getModelsApi().get(null, null, modelType.getId(), null);
+ for (Datum datum : models.getData()) {
+ Number modelId = (Number)datum.getAdditionalProperties().get("id");
+ String modelName = (String)datum.getAdditionalProperties().get("name");
+
+ Set groupsAddressed = new HashSet<>();
+ Set readersUnaddressed = new HashSet<>(readers);
+ Set editorsUnaddressed = new HashSet<>(editors);
+ ShareInfoRequest changeRequest = new ShareInfoRequest();
+
+ ResultList shares = this.getApsApi().getShareApi().getShareInfo(modelId.toString());
+ for (SharePermission share : shares.getData()) {
+ if (share.getGroup() != null) {
+ groupsAddressed.add(share.getGroup().getName());
+
+ if (PermissionLevel.Write.equals(share.getPermission())) {
+ if (editors.contains(share.getGroup().getName())) {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an editor of model '" + modelName + "'");
+ // no change
+ continue;
+ } else if (readers.contains(share.getGroup().getName())) {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' reverting from editor to reader of model '" + modelName + "'");
+ changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Read));
+ continue;
+ }
+ } else {
+ if (editors.contains(share.getGroup().getName())) {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' elevating from reader to editor of model '" + modelName + "'");
+ changeRequest.getUpdated().add(new PermissionLight().withId(share.getId()).withPermission(PermissionLevel.Write));
+ continue;
+ } else if (readers.contains(share.getGroup().getName())) {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' is already an reader of model '" + modelName + "'");
+ // no change
+ continue;
+ }
+ }
+
+ if (this.doRevoke) {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'; revoking ...");
+ changeRequest.getRemoved().add(new PermissionLight().withId(share.getId()));
+ } else {
+ this.getLog().debug("The named group '" + share.getGroup().getName() + "' is an unregulated editor of model '" + modelName + "'");
+ // not touching extra unnamed permissions
+ }
+ } else if (share.getPerson() != null) {
+ this.getLog().debug("Person-based model sharing not supported at this time; ignoring");
+ }
+ }
+
+ readersUnaddressed.removeAll(groupsAddressed);
+ for (String reader : readersUnaddressed) {
+ Long groupId = this.identityIndex.getValue(reader);
+ if (groupId == null) {
+ this.getLog().warn("The named group '" + reader + "' does not exist in APS; ignoring ...");
+ } else {
+ this.getLog().debug("The named group '" + reader + "' becoming a reader of model '" + modelName + "'");
+ changeRequest.getAdded().add(new PermissionLight().withGroupId(groupId).withPermission(PermissionLevel.Read));
+ }
+ }
+
+ editorsUnaddressed.removeAll(groupsAddressed);
+ for (String editor : editorsUnaddressed) {
+ Long groupId = this.identityIndex.getValue(editor);
+ if (groupId == null) {
+ this.getLog().warn("The named group '" + editor + "' does not exist in APS; ignoring ...");
+ } else {
+ this.getLog().debug("The named group '" + editor + "' becoming an editor of model '" + modelName + "'");
+ changeRequest.getAdded().add(new PermissionLight().withGroupId(groupId).withPermission(PermissionLevel.Write));
+ }
+ }
+
+ if (!changeRequest.getAdded().isEmpty() || !changeRequest.getUpdated().isEmpty() || !changeRequest.getRemoved().isEmpty()) {
+ this.getLog().info("Sharing model: " + modelType + " => '" + modelName + "'");
+ this.getApsApi().getShareApi().setShareInfo(modelId.toString(), changeRequest);
+ }
+ }
+ }
+
+ protected void normalizeParameters() {
+ Set readerSet = this.normalizeParameter(this.readers);
+ Set editorSet = this.normalizeParameter(this.editors);
+ this.appReaderSet = this.normalizeParameter(this.appReaders, readerSet);
+ this.appEditorSet = this.normalizeParameter(this.appEditors, editorSet);
+ this.processReaderSet = this.normalizeParameter(this.processReaders, readerSet);
+ this.processEditorSet = this.normalizeParameter(this.processEditors, editorSet);
+ this.formReaderSet = this.normalizeParameter(this.formReaders, readerSet);
+ this.formEditorSet = this.normalizeParameter(this.formEditors, editorSet);
+ }
+
+ private Set normalizeParameter(String parameter, Collection c) {
+ Set set = this.normalizeParameter(parameter);
+ set.addAll(c);
+ return set;
+ }
+
+ private Set normalizeParameter(String parameter) {
+ Set params = new HashSet<>();
+ if (parameter == null)
+ return params;
+ if (parameter.length() == 0)
+ return params;
+
+ String[] splitParams = parameter.split(",");
+ params.addAll(Arrays.asList(splitParams));
+ return params;
+ }
+
+ protected void buildIdentityIndex() {
+ List tenants = this.getApsApi().getAdminApi().getTenants();
+ for (Tenant tenant : tenants) {
+ List groups = this.getApsApi().getAdminApi().getGroups(tenant.getId(), true, true);
+ this.getLog().debug("Indexing groups: " + groups.size());
+ for (GroupLight group : groups)
+ this.identityIndex.put(group.getName(), group.getId());
+ if (this.getLog().isDebugEnabled())
+ this.getLog().debug("Indexed groups: " + this.identityIndex.toString());
+ }
+ }
+
+}