8 Commits

Author SHA1 Message Date
dbf5881b3b upgraded to v24.3.0; verified no re-compile needed 2024-10-15 16:00:48 -04:00
8870ade2d7 paging shared models 2024-09-04 11:11:47 -04:00
af8025237b fix HashableGroup equals 2024-07-15 15:36:13 -04:00
f7d74a082c APS v2.4.4 2024-07-08 10:04:39 -04:00
2cf1b241f3 get all models with ModelService 2024-07-08 10:04:31 -04:00
e437234c28 share models by tenant 2024-05-03 12:12:15 -04:00
5d172e234e fix enabled typo 2024-05-02 15:57:57 -04:00
185047e755 using @EnableScheduling 2024-04-22 13:04:06 -04:00
5 changed files with 158 additions and 39 deletions

View File

@@ -13,20 +13,24 @@ You can install this like any JAR extension. It just needs to be put in the web
There are several configuration properties available to shape how this extension shares models in your APS installation.
| Property | Default | Purpose |
| -------------------------------------------------------- | ------- | ------- |
| `inteligr8.modelShareExtension.groups.canRead` | | What groups should be granted read permission to models of all types. |
| `inteligr8.modelShareExtension.groups.canWrite` | | What groups should be granted write permission to models of all types. |
| `inteligr8.modelShareExtension.groups.canReadApps` | | What groups should be granted read permission to App models. |
| `inteligr8.modelShareExtension.groups.canWriteApps` | | What groups should be granted write permission to App models. |
| `inteligr8.modelShareExtension.groups.canReadProcesses` | | What groups should be granted read permission to Process models. |
| `inteligr8.modelShareExtension.groups.canWriteProcesses` | | What groups should be granted write permission to Process models. |
| `inteligr8.modelShareExtension.groups.canReadForms` | | What groups should be granted read permission to Form models. |
| `inteligr8.modelShareExtension.groups.canWriteForms` | | What groups should be granted write permission to Form models. |
| `inteligr8.modelShareExtension.shareAllModelsOnStartup` | `false` | Set to `true` to perform a startup scanning of models, sharing them according to the configuration. |
| `inteligr8.modelShareExtension.modelChunkSize` | `50` | When querying for all models, how many models should be queried per page. |
| `inteligr8.modelShareExtension.shareChunkSize` | `20` | When querying all user/group share permissions on a single model, how many records should be queried per page. |
| Property | Default | Purpose |
| -------------------------------------------------------- | -------- | ------- |
| `inteligr8.modelShareExtension.enabled` | `true` | Enablement; `false` will disable all the behaviors of this extension. |
| `inteligr8.modelShareExtension.scanDelayInMillis` | `30000` | The delay before the first scan for all models to share. |
| `inteligr8.modelShareExtension.scanIntervalInMillis` | `600000` | The interval between scans for all models to share. |
| `inteligr8.modelShareExtension.groups.canRead` | | What groups should be granted read permission to models of all types. |
| `inteligr8.modelShareExtension.groups.canWrite` | | What groups should be granted write permission to models of all types. |
| `inteligr8.modelShareExtension.groups.canReadApps` | | What groups should be granted read permission to App models. |
| `inteligr8.modelShareExtension.groups.canWriteApps` | | What groups should be granted write permission to App models. |
| `inteligr8.modelShareExtension.groups.canReadProcesses` | | What groups should be granted read permission to Process models. |
| `inteligr8.modelShareExtension.groups.canWriteProcesses` | | What groups should be granted write permission to Process models. |
| `inteligr8.modelShareExtension.groups.canReadForms` | | What groups should be granted read permission to Form models. |
| `inteligr8.modelShareExtension.groups.canWriteForms` | | What groups should be granted write permission to Form models. |
| `inteligr8.modelShareExtension.modelChunkSize` | `50` | When querying for all models, how many models should be queried per page. |
| `inteligr8.modelShareExtension.shareChunkSize` | `25` | When querying all user/group share permissions on a single model, how many records should be queried per page. |
You can specify these at startup as JVM system properties.
When specifying groups, use commas to separate each entry. You can also prefix each group with "sys:" or "org:" to target a specific system or organizational/functional group. The extension will query by group name and then external ID in each case.
Any specification of a specific group permission (e.g. `canReadApps`) will override any specification for the same group in the generic `canRead` or `canWrite` property.

10
pom.xml
View File

@@ -39,11 +39,11 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>17</maven.compiler.release>
<aps.version>2.4.1</aps.version>
<aps.version>24.3.0</aps.version>
</properties>
<dependencies>
@@ -154,7 +154,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.13</version>
<version>1.7.0</version>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://s01.oss.sonatype.org/</nexusUrl>

View File

@@ -17,6 +17,7 @@ package com.activiti.extension.conf;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* A means for injecting packages to scan for the Spring context.
@@ -24,6 +25,7 @@ import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGe
* @author brian@inteligr8.com
*/
@Configuration
@EnableScheduling
@ComponentScan(
basePackages = "com.inteligr8.alfresco.activiti.share",
nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class

View File

@@ -21,6 +21,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.activiti.domain.idm.User;
import com.activiti.security.SecurityUtils;
import com.activiti.service.api.UserService;
@Component
public class ModelShareExtension implements Bootstrappable {
@@ -28,6 +32,12 @@ public class ModelShareExtension implements Bootstrappable {
@Autowired
private ModelShareWorker worker;
@Autowired
private UserService userService;
@Value("${inteligr8.modelShareExtension.runAs:admin@app.activiti.com}")
private String runAsUser;
@Value("${inteligr8.modelShareExtension.enabled:true}")
private boolean enabled;
@@ -38,20 +48,35 @@ public class ModelShareExtension implements Bootstrappable {
public void onBootstrap() {
this.logger.trace("onBootstrap()");
if (this.enabled) {
this.bootstrapped = true;
this.logger.info("Model Share Extension initialized");
}
if (!this.enabled)
return;
this.logger.info("Model Share Extension enabled");
this.bootstrapped = true;
this.logger.info("Model Share Extension initialized");
}
public boolean isEnabled() {
return this.enabled;
}
// execute every 10 minutes
@Scheduled(fixedRate = 600000)
@Scheduled(
fixedRateString = "${inteligr8.modelShareExtension.scanIntervalInMillis:600000}",
initialDelayString = "${inteligr8.modelShareExtension.scanDelayInMillis:30000}"
)
private void scheduled() {
if (!this.enabled || !this.bootstrapped)
return;
this.logger.trace("scheduled()");
this.worker.share();
this.logger.debug("Assuming user: {}", this.runAsUser);
User runAsUser = this.userService.findUserByEmail(this.runAsUser);
SecurityUtils.assumeUser(runAsUser);
this.logger.debug("Assumed user: {}", runAsUser.getId());
this.worker.shareAllModels();
}
}

View File

@@ -25,6 +25,7 @@ import java.util.Map.Entry;
import java.util.Set;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.repository.ModelQuery;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,6 +35,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import com.activiti.domain.editor.AbstractModel;
import com.activiti.domain.editor.Model;
import com.activiti.domain.editor.ModelShareInfo;
import com.activiti.domain.editor.SharePermission;
@@ -104,8 +106,8 @@ public class ModelShareWorker implements Bootstrappable {
@Override
public void onBootstrap() {
this.logger.trace("onBootstrap({})");
this.logger.trace("onBootstrap()");
this.tenantId = this.tenantService.findTenantId();
this.groups = this.toMap(this.findGroups(this.readGroupNames), SharePermission.READ);
this.toMap(this.groups, this.findGroups(this.writeGroupNames), SharePermission.WRITE);
@@ -206,36 +208,110 @@ public class ModelShareWorker implements Bootstrappable {
map.put(key, value);
}
public void share() {
this.logger.trace("Discovering models ...");
public void shareAllModels() {
this.shareApsFormModels();
this.shareApsDataModels();
this.shareApsDecisionTableModels();
this.shareApsProcessModels();
this.shareApsAppModels();
if (this.tenantId != null) {
this.shareAllActivitiModels(this.tenantId);
} else {
this.shareAllActivitiModels(null);
}
}
private void shareApsFormModels() {
this.logger.trace("Discovering APS form models");
List<AbstractModel> models = this.modelService.getModelsByModelTypeAndReferenceId(Model.MODEL_TYPE_FORM, null);
for (AbstractModel model : models) {
this.logger.debug("Discovered APS form model: {} [{}]", model.getName(), model.getId());
this.shareModel((Model) model);
}
}
private void shareApsDataModels() {
this.logger.trace("Discovering APS data models");
List<AbstractModel> models = this.modelService.getModelsByModelTypeAndReferenceId(Model.MODEL_TYPE_DATA_MODEL, null);
for (AbstractModel model : models) {
this.logger.debug("Discovered APS data model: {} [{}]", model.getName(), model.getId());
this.shareModel((Model) model);
}
}
private void shareApsDecisionTableModels() {
this.logger.trace("Discovering APS decision table models");
List<AbstractModel> models = this.modelService.getModelsByModelTypeAndReferenceId(Model.MODEL_TYPE_DECISION_TABLE, null);
for (AbstractModel model : models) {
this.logger.debug("Discovered APS decision table model: {} [{}]", model.getName(), model.getId());
this.shareModel((Model) model);
}
}
private void shareApsProcessModels() {
this.logger.trace("Discovering APS process models");
List<AbstractModel> models = this.modelService.getModelsByModelTypeAndReferenceId(Model.MODEL_TYPE_BPMN, null);
for (AbstractModel model : models) {
this.logger.debug("Discovered APS process model: {} [{}]", model.getName(), model.getId());
this.shareModel((Model) model);
}
}
private void shareApsAppModels() {
this.logger.trace("Discovering APS app models");
List<AbstractModel> models = this.modelService.getModelsByModelTypeAndReferenceId(Model.MODEL_TYPE_APP, null);
for (AbstractModel model : models) {
this.logger.debug("Discovered APS app model: {} [{}]", model.getName(), model.getId());
this.shareModel((Model) model);
}
}
private void shareAllActivitiModels(Long tenantId) {
this.logger.trace("Discovering activiti models in tenant {} ...", tenantId);
long modelCount = 0L;
int page = 1;
int perPage = this.modelChunkSize;
List<org.activiti.engine.repository.Model> models = this.services.getRepositoryService().createModelQuery().listPage((page-1)*perPage, perPage);
ModelQuery query = this.services.getRepositoryService().createModelQuery()
.latestVersion();
if (tenantId != null)
query.modelTenantId(String.valueOf(tenantId));
else query.modelWithoutTenantId();
this.logger.trace("Discovering activiti models with page size: {}", perPage);
List<org.activiti.engine.repository.Model> models = query.listPage((page-1)*perPage, perPage);
if (models.isEmpty()) {
this.logger.debug("No activiti models; procDef count: {}", this.services.getRepositoryService().createProcessDefinitionQuery().latestVersion().count());
}
while (!models.isEmpty()) {
this.logger.trace("Fetched page #{} of {} models", page, models.size());
this.logger.trace("Fetched page #{} of {} activiti models in tenant {}", page, models.size(), tenantId);
for (org.activiti.engine.repository.Model model : models) {
this.logger.debug("Discovered model: {}: {}: {}", model.getCategory(), model.getId(), model.getName());
this.share(model);
this.shareModel(model);
}
modelCount += models.size();
page++;
models = this.services.getRepositoryService().createModelQuery().listPage((page-1)*perPage, perPage);
models = query.listPage((page-1)*perPage, perPage);
}
this.logger.trace("Discovered {} models", modelCount);
this.logger.trace("Discovered {} activiti models in tenant {}", modelCount, tenantId);
}
public void share(org.activiti.engine.repository.Model orgModel) {
public void shareModel(org.activiti.engine.repository.Model orgModel) {
Model model = (Model) this.modelService.getModel(Long.valueOf(orgModel.getId()));
this.shareModel(model);
}
public void shareModel(Model model) {
Map<HashableGroup, SharePermission> shares = this.fetchCurrentModelShares(model);
switch (model.getModelType()) {
case 3:
case Model.MODEL_TYPE_APP:
for (Entry<HashableGroup, SharePermission> group : this.groups.entrySet()) {
if (this.appDefsGroups.containsKey(group.getKey())) {
this.logger.trace("The default group permission is overridden by the app group permission: {}", group.getKey());
@@ -246,7 +322,7 @@ public class ModelShareWorker implements Bootstrappable {
for (Entry<HashableGroup, SharePermission> group : this.appDefsGroups.entrySet())
this.share(shares, model, group.getKey(), group.getValue());
break;
case 0:
case Model.MODEL_TYPE_BPMN:
for (Entry<HashableGroup, SharePermission> group : this.groups.entrySet()) {
if (this.processDefsGroups.containsKey(group.getKey())) {
this.logger.trace("The default group permission is overridden by the app group permission: {}", group.getKey());
@@ -257,7 +333,7 @@ public class ModelShareWorker implements Bootstrappable {
for (Entry<HashableGroup, SharePermission> group : this.processDefsGroups.entrySet())
this.share(shares, model, group.getKey(), group.getValue());
break;
case 1:
case Model.MODEL_TYPE_FORM:
for (Entry<HashableGroup, SharePermission> group : this.groups.entrySet()) {
if (this.formDefsGroups.containsKey(group.getKey())) {
this.logger.trace("The default group permission is overridden by the app group permission: {}", group.getKey());
@@ -284,9 +360,15 @@ public class ModelShareWorker implements Bootstrappable {
for (ModelShareInfo share : shares)
map.put(new HashableGroup(share.getGroup()), share.getPermission());
pageable = pageable.next();
shares = this.shareInfoRepo.findByModelIdOrderByShareDateAsc(model.getId(), pageable);
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Found existing shares: {}", map);
} else {
this.logger.debug("Found existing shares: {}", map.size());
}
return map;
}
@@ -342,7 +424,13 @@ public class ModelShareWorker implements Bootstrappable {
@Override
public boolean equals(Object obj) {
return this.group.getId().equals(((Group) obj).getId());
if (obj instanceof HashableGroup) {
return this.group.getId().equals(((HashableGroup) obj).getGroup().getId());
} else if (obj instanceof Group) {
return this.group.getId().equals(((Group) obj).getId());
} else {
return false;
}
}
@Override