- 1. Introduction
- 2. High Level Architecture
- 3. Multi Node (clustered) Setup
- 4. Licensing
- 5. Configuration properties file
- 6. Minimal configuration
- 7. General Server settings
- 8. Database Configuration
- 9. Business Calendar Settings
- 10. Initial User Created on First Start Up
- 11. Email Server Configuration
- 12. Elasticsearch Configuration
- 13. Application Access and default example app
- 14. Activiti Process Definition Cache
- 15. Content Storage
- 16. Microsoft Office Integration
- 17. Logging
- 18. External Identity Management (LDAP/Active Directory)
- 19. Embed the Activiti app in another application
- 20. Start and task form customisation
- 21. Custom form fields
- 22. Document Templates
- 23. Integration With External Systems
- 24. Custom Logic
- 25. Cookie configuration
- 26. REST API
- 26.1. Authentication
- 26.2. Activiti Open Source REST API
- 26.3. SkyVault Activiti BPM Suite API
- 26.3.1. Server Information
- 26.3.2. Profile
- 26.3.3. Runtime Apps
- 26.3.4. App Definitions List
- 26.3.5. App Import And Export
- 26.3.6. App Publish and Deploy
- 26.3.7. Process Definition Models List
- 26.3.8. Model Details and History
- 26.3.9. BPMN 2.0 Import and Export
- 26.3.10. Process Definitions
- 26.3.11. Start Form
- 26.3.12. Start Process Instance
- 26.3.13. Process Instance List
- 26.3.14. Get Process Instance Details
- 26.3.15. Delete a Process Instance
- 26.3.16. Task List
- 26.3.17. Task Details
- 26.3.18. Task Form
- 26.3.19. Completing a Task Form
- 26.3.20. Create a Standalone Task
- 26.3.21. Task Actions
- 26.3.22. User Task Filters
- 26.3.23. Comments
- 26.3.24. Checklists
- 26.3.25. User and Group lists
- 26.3.26. Thumbnails
- 26.3.27. Identity Management
- 27. Activiti Administrator
Version 1.3.1, August 2015
1. Introduction
This guide describes how to set up the SkyVault Activiti BPM Suite, a commercially supported suite of components built by SkyVault on top of the Activiti process engine (see Activiti Community BPM Platform).
This guide describes the various configuration options possible for the SkyVault Activiti BPM Suite. All engine-related topics such as how to use the BPMN 2.0 language to create process definitions, integrate with external systems, unit test your processes, and so on are documented in the Activiti Community User Guide, blogs or online articles.
2. High Level Architecture
Following diagram gives a high level overview of the technical components in the Activiti BPM Suite:
The SkyVault Activiti BPM Suite is packaged as a regular Java Web application (WAR file) that is deployable on any supported Java web container. The WAR file contains both the Java logic, the REST API resources and the user interface html and javascript files. The application is stateless, which means it does not use any sessions, and requests can be handled by any node in a clustered setup (see later for more information on multi-node setup).
Some technical implementation details:
-
The Activiti process engine (enterprise edition) is embedded within the SkyVault Activiti BPM Suite and directly used through its Java API.
-
The REST API has two parts:
-
The REST API that exposes the Actviti API directly (see the Activiti User Guide) . Note that a user with a specific role (tenant admin or tenant manager) is needed to access this part of the REST API, for security reasons.
-
The REST API that exposes operations in the context of the applications which are part of the SkyVault Activiti BPM suite application. This REST API is used by the SkyVault Activiti BPM Suite user interface
-
-
The application requires Java 7 and is compliant with JEE 6 technologies. The Activiti Engine itself also supports Java 6, but due to components such as Elasticsearch, the SkyVault Activiti BPM Suite requires Java 7.
-
The backend logic specific to the SkyVault Activiti BPM Suite logic is implemented using Spring 4 and JPA (Hibernate).
-
All user interfaces are written using HTML5 and AngularJS
The SkyVault Activiti BPM Suite uses following external systems:
-
A relational database
-
An elasticsearch installation. Note that the application ships with an embedded elasticsearch by default which requires little configuration.
-
A file system (shared file system in multi-node setup) where content is stored
-
An identity manager store (LDAP or Active Directory) is optional. By default, a database-backed user and group store is used.
The Activiti process engine used within the SkyVault Activiti BPM Suite can be managed using the Activiti Administrator application. This is also provided as a WAR file with SkyVault Activiti BPM Suite distributions.
The Activiti Designer is an Eclipse plugin that can be used by developers to create BPMN 2.0 process definitions within their Eclipse IDE. It is possible to configure the plugin in such a way that it can pull and push process definitions model to the SkyVault Activiti BPM Suite application. For more information on the Designer plugin, see the Activiti Designer User Guide.
The application can also connect to a SkyVault 2.0 installation or to Google Drive (not shown on the diagram).
3. Multi Node (clustered) Setup
Running the application on multiple servers, for performance, resilience or failover reasons, is straightforward. The application is architected to be stateless. This means that any server can handle any request from any user. When using multiple servers, it is enough to have a traditional load balancer (or proxy) in front of the servers running the SkyVault Activiti BPM Suite application. Scaling out is done in a "horizontal" way, by simply adding more servers behind the load balancer.
Do note that each of the servers will connect to the same relational database. While scaling out by adding more servers, do not forget to also make sure the database can handle the additional load.
4. Licensing
The SkyVault Activiti BPM Suite needs a valid license to work properly. This license is provided by SkyVault in the form of a file named activiti.lic. This file should be placed either:
-
on the classpath of the web application
-
in the home folder of the user used to start the web container, in the $USER_HOME/.activiti/enterprise-license/ (note the dot before activiti) folder.
5. Configuration properties file
The SkyVault Activiti BPM Suite is configured through a properties file named activiti-app.properties. This file must be on the classpath to be found. The following options are possible in relation to this properties file:
-
An activiti-app.properties file with default values can be found in the WAR file (or exploded WAR folder) in the WEB-INF/classes/META-INF/activiti-app folder.
-
An activiti-app.properties file with custom values can be placed on the classpath. For example: the WEB-INF/classes folder of the WAR, the /lib folder of Tomcat or other places specific to the web container being used.
The values of a configuration file on the classpath have precedence over the values in the WEB-INF/classes/META-INF/activiti-app/activiti-app.properties file.
6. Minimal configuration
Minimally, the application needs the following settings to run:
-
The database connection needs to be configured using either JDBC or a JNDI datasource
-
The correct Hibernate dialect needs to be set
All the other properties have defaults that should not stop the application from starting and working.
7. General Server settings
The following properties are general server settings. While they do have defaults, it might be necessary to change them:
Property | Description | Default |
---|---|---|
server.contextroot |
The context root on which the user accesses the application. This is used in various places to generate urls to correct resources. |
activiti-app |
security.rememberme.key |
A key that is used for cookie validation. In a multi node setup, all nodes must have the same value for this property. |
somekey |
8. Database Configuration
8.1. Using JDBC Connection Parameters
Following properties need to be set to change the database:
Property | Description |
---|---|
datasource.driver |
The JDBC driver used to connect to the database. Note that the driver must be on the classpath of the web application |
datasource.url |
The JDBC url used to connect to the database |
datasource.username |
The user of the database system that is used to connect to the database |
datasource.password |
The password of the above user |
Example:
1
2
3
4
5 datasource.driver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://127.0.0.1:3306/activiti?characterEncoding=UTF-8
datasource.username=SkyVault
datasource.password=SkyVault
8.2. Connection Pooling
When using JDBC parameters to connect to the database, it is also possible to tweak the connection pooling settings to better suit the anticipated load. Following properties can be set:
Property | Description | Default |
---|---|---|
datasource.min-pool-size |
The minimum number of connections in the connection pool. |
5 |
datasource.max-pool-size |
The maximum number of connections in the connection pool. |
100 |
datasource.acquire-increment |
The number of additional connections the system will try to acquire each time the connection pool is exhausted. |
5 |
datasource.preferred-test-query |
The query used to verify that the connection is still valid |
No default value (not a required property). The value depends on the database: select 1 for H2, MySQL, PostgreSQL and Microsoft SQL Server, SELECT 1 FROM DUAL for Oracle and SELECT current date FROM sysibm.sysdummy1 for DB2. |
datasource.test-connection-on-checkin |
Boolean value. If true, an operation will be performed asynchronously on every connection checkin to verify that the connection is valid. For best performance, a proper datasource.preferred-test-query should be set. |
true |
datasource.test-connection-on-checkout |
Boolean value. If true, an operation will be performed asynchronously on every connection checkout to verify that the connection is valid. Testing Connections on checkout is the simplest and most reliable form of Connection testing. For best performance, a proper datasource.preferred-test-query should be set. |
true |
datasource.max-idle-time |
The number of seconds a connection can be pooled before being discarded. |
1800 |
datasource.max-idle-time-excess-connections |
Number of seconds that connections in excess of minPoolSize should be permitted to remain idle in the pool before being discarded. The intention is that connections remain in the pool during a load spike. |
1800 |
The connection pooling framework used is C3P0. It has extensive documentation on the settings described above.
8.3. Using a JNDI Datasource
When a JNDI datasource is configured in the web container or application server, the JNDI name needs to be configured with following properties:
Property | Description | Default |
---|---|---|
datasource.jndi.name |
The JNDI name of the datasource. This varies depending on the application server or web container. |
jdbc/activitiDS |
datasource.jndi.resourceRef |
Set whether the lookup occurs in a J2EE container, i.e. if the prefix "java:comp/env/" needs to be added if the JNDI name doesn’t already contain it. |
true |
Example (on JBoss EAP 6.3):
1 datasource.jndi.name=java:jboss/datasources/activitiDS
8.4. Hibernate Settings
The SkyVault Activti BPM Suite specific logic is written using JPA 2.0 with Hibernate as implementation. Note that the Activiti Process Engine itself uses MyBatis for full control of each SQL query.
The following settings need to be set:
Property | Description | Mandatory |
---|---|---|
hibernate.dialect |
The dialect implementation that Hibernate uses. This is database specific. |
Yes. Very important to set the correct dialect, or the app might not even boot up! |
The following values are those that are used to test the SkyVault Activiti BPM Suite:
Database | Dialect |
---|---|
H2 |
org.hibernate.dialect.H2Dialect |
MySQL |
org.hibernate.dialect.MySQLDialect |
Oracle |
org.hibernate.dialect.Oracle10gDialect |
SQL Server |
org.hibernate.dialect.SQLServerDialect |
DB2 |
org.hibernate.dialect.DB2Dialect |
PostgresQL |
org.hibernate.dialect.PostgreSQLDialect |
Optionally, the hibernate.show_sql property can be set to true if the SQL being executed needs to be printed to the log.
9. Business Calendar Settings
Business Calendar is used to calculate tasks' relative due dates. To exclude weekends when calculating tasks' relative due dates, calendar.weekends property can be set as follows:
1
2 # Weekend days comma separated (day's first 3 letters in capital)
calendar.weekends=SAT,SUN
10. Initial User Created on First Start Up
When the application starts for the first time, it will verify that there is at least one user in the system. If not, a user with superuser rights will be created.
The default user id to sign in with is admin@app.activiti.com using password admin. This should be changed after signing in for the first time.
The initial user details can be modified (must be done before first start up) with following properties:
Property | Description |
---|---|
admin.email |
The email address used to create the first user, which also acts as the sign in identifier. |
admin.group |
Capabilities in the SkyVault Activiti BPM Suite are managed by adding users into certain groups. The first user will have all capabilities enabled. This property defines the name of the group to which the first user will be added. By default 'Superusers'. |
11. Email Server Configuration
The application sends out emails to users on various events (for example, when a task is assigned to the user).
Following properties must be set to configure the email server:
Property | Description |
---|---|
email.enabled |
Enables or disables the email functionality as a whole. By default false, so make sure to set it to true when needing email functionality. |
email.host |
The host address of the email server. |
email.port |
The port on which the email server is running. |
email.useCredentials |
Boolean value. Indicates if the email server needs credentials to make a connection. If so, both username and password need to be set. |
email.username |
The username used as credentials when email.useCredentials is true. |
email.password |
The password used as credentials when email.useCredentials is true. |
email.ssl |
Defines if SSL is needed for the connection to the email server. |
email.tls |
Defines if TLS is needed for the connection to the email server. This needs to be true when Google mail is used as the mail server for example. |
email.from.default |
The email address that is used in the from field of any email sent. |
email.from.default.name |
The name that is used in the from field of the email sent. |
email.feedback.default |
Some emails will have a feedback email address that people can use to send feedback. This property defines this. |
Emails are created by a templating engine. The emails can contain various links to the runtime system to bring the user straight to the correct page in the web application.
The following property need to be set to make these links correct. The example in the table below uses 'localhost' as host address and 'activiti-app' as the context root:
Property | Example |
---|---|
email.base.url |
12. Elasticsearch Configuration
Elastic search is an open source data store for json documents. Its main features include fast full text search and analytics.
Elasticsearch is used within the SkyVault Activiti BPM Suite as a data store for generating analytics and reports. The full text search capabilities are not currently used, but will be in a future release.
There are two ways Elasticsearch can be configured in the application:
-
Embedded: the Elasticsearch server starts up embedded within the SkyVault Activiti BPM Suite. Multiple instances of this application will also have multiple instances of Elasticsearch. These Elasticsearch servers will also find each other and act like a regular Elasticsearch cluster.
-
Client: the application only creates a client, which connects to an Elasticsearch cluster. This approach is much like connecting to a relational database.
One thing to keep in mind is that an Elasticsearch client will always be created. In the Client use case, this is all that will be instantiated. In the Embedded setup, an Elasticsearch client will be created that connects to a cluster of which the local node is part. In Elasticsearch terminology, a client node and a data node.
12.1. General Settings
The following properties are applicable to both the embedded and client setup:
Property | Description | Default |
---|---|---|
elastic-search.cluster.name |
The name of the Elasticsearch cluster to connect to |
activiti-elastic-search-cluster |
elastic-search.node.name |
The name of the node. The client node will have this name plus the suffix -client. In the embedded setup there will also be a data node with a -data suffix. |
activiti |
elastic-search.default.index.name |
The name of the index in which the data will be stored. Only change this if there is a nameclash for some reason in your Elasticsearch installation |
activiti |
elastic-search.tenant.index.prefix |
When running the SkyVault Activiti BPM Suite with multi-tenancy, each tenant has its own index alias. Change this value to change the prefix applied to the alias. |
activiti_tenant_ |
elastic-search.enable.http |
Enables the HTTP REST API of Elasticsearch. It is advised not to set this to true, unless traffic to it is strictly controlled by firewall rules. |
false |
Elasticsearch nodes (both client and data Elasticsearch nodes, so this applies for both embedded and client setups) need to find each other to work in a clustered setup. By default, it will use multicast to discover what other nodes are available on the network. This is the default. Alternatively, unicast can also be used or the multicast settings can be tweaked.
To change the type of discovery:
Property | Description | Default |
---|---|---|
elastic-search.discovery.type |
The way nodes find each other: multicast or unicast. |
multicast |
When using multicast, the following properties can be set:
Property | Description | Default |
---|---|---|
elastic-search.discovery.multicast.group |
The multicast group address to use. |
224.2.2.4 |
elastic-search.discovery.multicast.port |
The multicast port to use. |
54328 |
elastic-search.discovery.multicast.ttl |
The time-to-live of the multicast message. |
3 |
elastic-search.discovery.multicast.address |
The address to bind to. |
All available network interfaces (0.0.0.0) |
When using unicast, only one property needs to be set:
Property | Description | Default |
---|---|---|
elastic-search.discovery.hosts |
The way nodes find each other: multicast or unicast. |
Either an array setting or a comma delimited setting. Each value is either in the form of host:port, or in the form of host[port1-port2] |
12.2. Embedded Setup
This is the default configuration. The following properties need to be set when using Elasticsearch in an embedded setup:
Property | Description | Default |
---|---|---|
elastic-search.server.type |
embedded |
embedded |
elastic-search.data.path |
Defines where Elasticsearch will store its data on disk. $user_home$ can be used in the path. Make sure the application or application server has the right privileges to write to this path. To backup the Elasticsearch data easily, simply backup the content of this folder. |
$user_home$/activiti-elastic-search-data |
Each embedded Elasticsearch server will behave as a regular Elasticsearch server. For example, when running multiple nodes, the embedded Elasticsearch servers will distribute the data and make sure queries on the data are routed to the correct server.
12.3. Client Setup
To connect to an externally running Elasticsearch cluster, set following property. Combined with the general settings above, that’s all that is needed.
Property | Description | Default |
---|---|---|
elastic-search.server.type |
multicast-cluster |
multicast-cluster |
Note that no data is stored on the server on which the application is running (contrary to the embedded setup). The data fully resides withing the externally managed Elasticsearch cluster.
The version used in the application is Elasticsearch 1.3.2. While not mandatory, it is recommended to use the same version as the library JAR.
12.4. Disabling Elasticsearch
To disable Elasticsearch (embedded or the client), set the elastic-search.server.type property to none.
Note that the Analytics component will no longer work.
12.5. Event Processing for analytics
The event processing is closely related to the Elasticsearch configuration. The main concept is depicted in the diagram below.
-
The Activiti Process Engine is configured in such a way that it generates events for everything happening related to process execution (processes started, task completed, etc). These events are stored in the database (such that there is no problem with transactionality, in the sense that writing the events to the database succeeds or fails with the regular Activiti process execution data).
-
A component called event processor will asynchronously check for new entries in the database table for the events. The events will be processed and transformed to JSON.
-
The JSON event is asynchronously sent to Elasticsearch. From that point on the data will show up in the reports.
The event processor is architected in a way that it works without collisions in a multi node clustered setup. Each of the event processors will first try to lock events before processing them. If a node goes down during event processing (after locking), an expired events processor component will pick them up and process them as regular events.
The event processing can be configured, but leaving the default values should cater for typical scenarios.
Property | Description | Default |
---|---|---|
event.generation.enabled |
Set to false if no Activiti events need to be generated. Do note that the reporting/analytics event data is then lost forever. |
true |
event.processing.enabled |
Set to false to not to event processing. This can be useful in a clustered setup where only sone nodes do the processing. |
true |
event.processing.blocksize |
The number of events that are attempted to be locked and fetched to be processed in one transaction. Larger values equate to more memory usage, but less database traffic. |
100 |
event.processing.cronExpression |
The cron expression that defines how often the events generated by the Activiti process engine are processed (i.e. read from the database and fed into Elastic Search). By default 30 seconds. If events do not need to appear quickly in the analytics, it is advised to make this less frequent to put less load on the database. |
0/30 * * * * ? |
event.processing.expired.cronExpression |
The cron expression that defines how often 'expired' events are processed. These are events that were locked, but never processed (such as when the node processing them went down). |
0 0/30 * * * ? |
event.processing.max.locktime |
The maximum time an event can be 'locked' before it is seen as expired. After that it can be taken by another processor. Expressed in milliseconds. |
600000 |
event.processing.processed.events.action |
To keep the database table where the Activiti Process Engine writes the events small and efficient, processed events are either moved to another table or deleted. Possible values are move and delete. Move is the safe option, as it allows for reconstructing the Elasticsearch index if the index was to get corrupted for some reason. |
move |
event.processing.processed.action.cronExpression |
The cron expression that defines how often the action above happens. |
0 25/45 * * * ? |
13. Application Access and default example app
It is possible to configure whether users get access to the model editors (the Kickstart application) and the analytics application.
Access to the default application is configured through capabilities. In the admin UI, it is possible to create so-called 'system groups'. These groups have a set of capabilities. All users part of that group have those capabilities.
The following settings configure app access when a new user is created in the system (manuall or through LDAP sync). To enable access, set the property app.[APP-NAME].default.enabled to true. If true, a newly created user will be given access to this app.
The access is configured by adding the user to a group with a certain capability that enabled the app. The name of that group can be configured using the app.[APP-NAME].default.capabilities.group property. If this property is set, and the app.[APP-NAME].default.enabled property is set to true, the group with this name will be used to add the user to and provide access to the app. If the group does not exist, it is created. If the property is commented, and app.[APP-NAME].default.enabled property, a default name is used.
Currently possible app names: { analytics | kickstart }
Property | default |
---|---|
app.analytics.default.enabled |
true |
app.analytics.default.capabilities.group |
analytics-users |
app.kickstart.default.enabled |
true |
app.kickstart.default.capabilities.group |
kickstart-users |
The following setting, if set to 'true', will create a default example app with some simple review and approve processes for every newly created user.
Property | default |
---|---|
app.review-workflows.enabled |
false |
14. Activiti Process Definition Cache
The Activiti Process Engine operates in a stateless way. But there is, of course, data that will never change, which makes it a prime candidate for caching.
A process definition is an example of such ‘static data’. When you deploy a BPMN 2.0 XML file to the Activiti engine, the engine parses it to something it can execute, and stores the xml and some data, such as the description, business key, in the database. Such a process definition will never change. Once it’s in the database, the stored data will remain the same until the process definition is deleted.
On top of that, parsing a BPMN 2.0 XML to something executable is quite a costly operation compared with other engine operations. This is why the Activiti engine internally uses a process definition cache to store the parsed version of the BPMN 2.0 XML.
In a multi node setup, each node will have a cache of process definitions. When a node goes down and comes up, it will rebuild the cache as it handles process instances, tasks. and so on.
The process definition cache size can be set by the following property:
Property | Description | Default |
---|---|---|
activiti.process-definitions.cache.max |
The number of process definitions kept in memory. When the system needs to cope with many process definitions concurrently, it is advised to make this value higher than the default. |
128 |
15. Content Storage
In various places in the SkyVault Activiti BPM Suite it is possible to upload content, such as attaching a file to a task or a form. This content is stored on disk, with the path being configured as follows:
1 contentstorage.fs.rootFolder=data/
Very important when using multiple instances of the application is that this path references a shared network drive. The reason is that all nodes must be able to access all content (as the application is stateless and any server can handle any request).
16. Microsoft Office Integration
The Microsoft Office integration (opening an Office document directly from the browser) doesn’t need any specific configuration. However, the protocol used to do this does mandates the use of HTTPS servers by default. This means that the SkyVault Activiti BPM Suite needs to run on a server that has HTTPS and its certificates correctly configured.
If this is not possible for some reason, a setting will need to be changed on the machines for each user to make this feature work.
For Windows, see: http://support.microsoft.com/kb/2123563
For OS X, execute following terminal command:
defaults -currentHost write com.microsoft.registrationDB hkey_current_user\\hkey_local_machine\\software\\microsoft\\office\\14.0\\common\\internet\\basicauthlevel -int 2
Note that this is not advised from a security point of view.
17. Logging
The application uses SLF4J bounded to Log4j. The log4j.properties configuration file can be found in the WEB-INF/classes folder of the WAR file.
17.1. Logging Backend Metrics
For all REST API endpoints available in the application, metrics are gathered about runtime performance. These statistics can be written to the log.
Property | Description | Default |
---|---|---|
metrics.console.reporter.enabled |
Boolean value. If true, the REST API endpoint statistics will be logged. |
false |
metrics.console.reporter.interval |
The interval of logging in seconds. Do note that these logs are quite large, so this should not be set to be too frequent. |
60 |
Note that the statistics are based on the runtime timings since the last start up. When the server goes down, the metrics are lost.
Example output for one REST API endpoint:
com.activiti.runtime.rest.TaskQueryResource.listTasks count = 4 mean rate = 0.03 calls/second 1-minute rate = 0.03 calls/second 5-minute rate = 0.01 calls/second 15-minute rate = 0.00 calls/second min = 5.28 milliseconds max = 186.55 milliseconds mean = 50.74 milliseconds stddev = 90.54 milliseconds median = 5.57 milliseconds 75% <= 141.34 milliseconds 95% <= 186.55 milliseconds 98% <= 186.55 milliseconds 99% <= 186.55 milliseconds 99.9% <= 186.55 milliseconds
18. External Identity Management (LDAP/Active Directory)
It’s possible to hook up a centralized user data store with the SkyVault Activiti BPM Suite. Any server supporting the LDAP protocol can be used. Special configuration options and logic has been included to work with Active Directory (AD) systems too.
From a high-level point of view, the external Identity Management (IDM) integration works as follows:
-
Periodically, all user and group information is synchronized asynchronically. This means that all data for users (name, email address, group membership, etc) is copied to the Activiti database.
-
The is done is both for performance and to be able to efficiently store more data about the user that doesn’t belong in the IDM system.
-
If the user signs into the SkyVault Activiti BPM Suite, the authentication request is passed to the IDM system. On successful authentication there, the user data corresponding to that user is fetched from the Activiti database and used for the various requests. Note that no passwords are saved in the Activiti database when using an external IDM.
Note that the LDAP sync only needs to be activated and configured on one node in the cluster (but it works when activated on multiple nodes, but this will of course lead to higher traffic for both the LDAP system and the database).
18.1. Configuration
The configuration of the external IDM authentication/synchronization is done in the same way as the regular properties. There is a properties file named activiti-ldap.properties in the WEB-INF/classes/META-INF/ folder in the WAR file. The values in a file with the same name on the classpath have precedence over the default values in the former file.
In the same folder, there is also a file 'example-activiti-ldap-for-ad.properties' which contains an example configruation for an Active Directory system.
18.2. Server Connection Configuration
The following snippet shows the properties involved in configuring a connection to an LDAP server (Activity Directory is similar). These are the typical parameters used when connecting with an LDAP server. Advanced parameters are commented out in the example below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 # The URL to connect to the LDAP server
ldap.authentication.java.naming.provider.url=ldap://localhost:10389
# The default principal to use (only used for LDAP sync)
ldap.synchronization.java.naming.security.principal=uid=admin,ou=system
# The password for the default principal (only used for LDAP sync)
ldap.synchronization.java.naming.security.credentials=secret
# The authentication mechanism to use for synchronization
#ldap.synchronization.java.naming.security.authentication=simple
# LDAPS truststore configuration properties
#ldap.authentication.truststore.path=
#ldap.authentication.truststore.passphrase=
#ldap.authentication.truststore.type=
# Set to 'ssl' to enable truststore configuration via subsystem's properties
#ldap.authentication.java.naming.security.protocol=ssl
# The LDAP context factory to use
#ldap.authentication.java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
# Requests timeout, in miliseconds, use 0 for none (default)
#ldap.authentication.java.naming.read.timeout=0
# See http://docs.oracle.com/javase/jndi/tutorial/ldap/referral/jndi.html
#ldap.synchronization.java.naming.referral=follow
It is possible to configure connection pooling for the LDAP/AD connections. This is an advanced feature and only needed when creating a connection to the IDM system has an impact on system performance.
The connection pooling is implemented using the Spring-LDAP framework. Below are all the properties possible to configure. These follow the semantics of the properties possible for Spring-LDAP and are are described here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 # -----------------------
# LDAP CONNECTION POOLING
# -----------------------
# Options=
# nothing filled in: no connection pooling
# 'jdk': use the default jdk pooling mechanism
# 'spring': use the spring ldap connection pooling facilities. These can be configured further below
#ldap.synchronization.pooling.type=spring
# Following settings follow the semantics of org.springframework.ldap.pool.factory.PoolingContextSource
#ldap.synchronization.pooling.minIdle=0
#ldap.synchronization.pooling.maxIdle=8
#ldap.synchronization.pooling.maxActive=0
#ldap.synchronization.pooling.maxTotal=-1
#ldap.synchronization.pooling.maxWait=-1
# Options for exhausted action: fail | block | grow
#ldap.synchronization.pooling.whenExhaustedAction=block
#ldap.synchronization.pooling.testOnBorrow=false
#ldap.synchronization.pooling.testOnReturn=false
#ldap.synchronization.pooling.testWhileIdle=false
#ldap.synchronization.pooling.timeBetweenEvictionRunsMillis=-1
#ldap.synchronization.pooling.minEvictableIdleTimeMillis=1800000
#ldap.synchronization.pooling.numTestsPerEvictionRun=3
# Connection pool validation (see http://docs.spring.io/spring-ldap/docs/2.0.2.RELEASE/reference/#pooling for semantics)
# Used when any of the testXXX above are set to true
#ldap.synchronization.pooling.validation.base=
#ldap.synchronization.pooling.validation.filter=
# Search control: object, oneLevel, subTree
#ldap.synchronization.pooling.validation.searchControlsRefs=
18.3. Authentication
To enable authentication via LDAP or AD, set the following property:
1 ldap.authentication.enabled=true
In some organisations, a case insensitive login is allowed with the ldap id. By default , this is disabled. To enable, set following property to false.
1 ldap.authentication.casesensitive=false
Next, a property ldap.authentication.dnPattern can be set:
1 ldap.authentication.dnPattern=uid={0},ou=users,dc=alfresco,dc=com
If the users are in a flat list (e.g. one organizational unit), it’s easy: simply set the property to a value, (uid={0},ou=users,dc=alfresco,dc=com). This is also the most performant way, as the LDAP bind can be done directly.
The ID with which the user signs in will be passed into the template above (the {0} will be replaced) and the password will be validated.
However, if the users are in structured folders (organizational units for example), a direct pattern cannot be used. In this case, leave the property either empty or comment it out. Now, a query will be performed using the ldap.synchronization.personQuery (see below) with the ldap.synchronization.userIdAttributeName to find the user and their distinguished (DN) name. That DN will then be used to sign in.
When using Active Directory, two additional properties need to be set:
1
2 ldap.authentication.active-directory.enabled=true
ldap.authentication.active-directory.domain=SkyVault.com
The first property enables Active Directory support. The second one is the domain that needs to be added to the user ID (i.e. userId@domain) to sign in using Active Directory.
In case the domain does not match with the rootDn, it is possible to set is explicitly:
ldap.authentication.active-directory.rootDn=DC=somethingElse,DC=com
And also the filter that is used(which defaults to a userPrincipalName comparison) can be changed:
ldap.authentication.active-directory.searchFilter=(&(objectClass=user)(userPrincipalName={0}))
18.4. Synchronization
The synchronization component will periodically query the IDM system and change the Actviti user and group database. There are two synchronization 'modes': full and differential.
Full synchronization queries all data from the IDM and checks every user, group and membership to be valid. In terms of resource usage, it is obviously heavier than the differential synchronization. On a first start up of the SkyVault Activiti BPM Suite configured to use an external IDM, the full synchronization will be run so all user and group data is available in the database.
Full synchronization needs to be enabled. The frequency in which it runs is set using a cron expression:
1
2 ldap.synchronization.full.enabled=true
ldap.synchronization.full.cronExpression=0 0 0 * * ?
Differential synchronization is 'lighter', in terms of performance, as it only queries the users and groups that have changed since the last synchronization. One downside is that it cannot detect deletions of users and groups. Consequently, a full synchronization needs to run periodically (but less than a differential synchronization typically) to account for these deletions.
1
2 ldap.synchronization.differential.enabled=true
ldap.synchronization.differential.cronExpression=0 0 */4 * * ?
Do note that all synchronization results are logged, both in the regular logging and in a database table named IDM_SYNC_LOG
The synchronization logic builds on two elements:
-
Queries that return the correct user/group/membership data
-
A mapping of LDAP attributes to attributes used within the Activiti system
There are a lot of properties to configure, so do base your configuration on one of the two files in the META-INF folder, as these contain default values. You only need to add the specific properties to your custom configuration file if the default values are not appropriate.
18.4.1. Generic Synchronization settings
These are settings that are generic or shared between user and group objects. For each property, an example setting of a 'regular' LDAP system (i.e. ApacheDS) and Active Directory is shown.
Property | Description | LDAP Example | Active Directory Example |
---|---|---|---|
ldap.synchronization.distinguishedNameAttributeName |
The attribute that is the 'disinguished name' in the system. |
dn |
dn |
ldap.synchronization.modifyTimestampAttributeName |
The name of the operational attribute recording the last update time for a group or user. Important for the differential query. |
modifyTimestamp |
whenChanged |
ldap.synchronization.createTimestampAttributeName |
The name of the operational attribute recording the create time for a group or user. Important for the differential query. |
createTimestamp |
whenCreated |
ldap.synchronization.timestampFormat |
The timestamp format. This is specific to the directory servers and can vary. |
yyyyMMddHHmmss.SSS’Z' |
yyyyMMddHHmmss'.0Z' |
ldap.synchronization.timestampFormat.locale.language |
The timestamp format locale language for parsing. Follows the java.util.Locale semantics. |
en |
en |
ldap.synchronization.timestampFormat.locale.country |
The timestamp format locale country. Follows the java.util.Locale semantics. |
GB |
GB |
ldap.synchronization.timestampFormat.timezone |
The timestamp format timezone. Follows the java.text.SimpleDateFormat semantics. |
GMT |
GMT |
18.4.2. User Synchronization Settings
Property | Description | LDAP Example | Active Directory Example |
---|---|---|---|
ldap.synchronization.userSearchBase |
The user search base restricts the LDAP user query to a sub section of a tree on the LDAP server. |
ou=users,dc=alfresco,dc=com |
ou=users,dc=alfresco,dc=com |
ldap.synchronization.personQuery |
The query to select all objects that represent the users to import (used in the *full synchronization query*ß). |
(objectclass\=inetOrgPerson) |
(&(objectclass\=user)(userAccountControl\:1.2.840.113556.1.4.803\:\=512)) |
ldap.synchronization.personDifferentialQuery |
The query to select objects that represent the users to import that have changed since a certain time (used in the differential synchronization query). |
(&(objectclass\=inetOrgPerson)(!(modifyTimestamp<\={0}))) |
(&(objectclass\=user)(userAccountControl\:1.2.840.113556.1.4.803\:\=512)(!(whenChanged<\={0}))) |
ldap.synchronization.userIdAttributeName |
The attribute name on people objects found in LDAP to use as the user ID in SkyVault |
uid |
cn |
ldap.synchronization.userFirstNameAttributeName |
The attribute on person objects in LDAP to map to the first name property of a user |
givenName |
givenName |
ldap.synchronization.userLastNameAttributeName |
The attribute on person objects in LDAP to map to the last name property of a user |
sn |
cn |
ldap.synchronization.userEmailAttributeName |
The attribute on person objects in LDAP to map to the email property of a user |
||
ldap.synchronization.userType |
The person type in the directory server. |
inetOrgPerson |
user |
It is also possible configure what users should be made administrators in the system. When using multi-tenancy, the administrator of all tenants can be configured as follows. Delimit multiple entries with ; as commas can’t be used. Note: no trimming of spaces will be applied.
1 ldap.synchronization.tenantAdminDn=uid=joram,ou=users,dc=alfresco,dc=com;uid=tijs,ou=users,dc=alfresco,dc=com
Add specific people who need to become tenant admins, or when not using multi-tenancy. Similar rules for delimiting apply as above.
1 ldap.synchronization.tenantManagerDn=uid=joram,ou=users,dc=alfresco,dc=com
It’s important to set at least 1 user with admin rights. Otherwise no user will be able to sign into the system and administer it.
18.4.3. Group Synchronization Settings
Property | Description | LDAP Example | Active Directory Example |
---|---|---|---|
ldap.synchronization.groupSearchBase |
The group search base restricts the LDAP group query to a sub section of a tree on the LDAP server. |
ou=groups,dc=alfresco,dc=com |
ou=groups,dc=alfresco,dc=com |
ldap.synchronization.groupQuery |
The query to select all objects that represent the groups to import (used in full synchronization). |
(objectclass\=groupOfNames) |
(objectclass\=group) |
ldap.synchronization.groupDifferentialQuery |
The query to select objects that represent the groups to import that have changed since a certain time (used in the differential synchronization). |
(&(objectclass\=groupOfNames)(!(modifyTimestamp<\={0}))) |
(&(objectclass\=group)(!(whenChanged<\={0}))) |
ldap.synchronization.groupIdAttributeName |
The attribute on LDAP group objects to map to the authority name property in Activiti. |
cn |
cn |
ldap.synchronization.groupMemberAttributeName |
The attribute in LDAP on group objects that defines the DN for its members. This is an important setting as is defines group memberships of users and parent-child relations between groups. |
member |
member |
ldap.synchronization.groupType |
The group type in LDAP. |
groupOfNames |
group |
18.4.4. Paging
It is possible to use paging when connecting to an LDAP server (some even mandate this).
To enable paging when fetching users or groups, set following properties:
1
2 ldap.synchronization.paging.enabled=true
ldap.synchronization.paging.size=500
By default, paging is disabled
18.4.5. Batch insert
It is possible to tweak the batch size when doing an LDAP sync.
The insert batch size limits the amount of data being inserted in one transaction (e.g. 100 users per transactions are inserted). By default, this is 5. The query batch size is used when data is fetched from the Activiti database (e.g. fetching users to check for deletions when doing a full sync).
1
2 ldap.synchronization.db.insert.batch.size=100
ldap.synchronization.db.query.batch.size=100
19. Embed the Activiti app in another application
The components of the Activiti app can be included in an existing / other application by referencing the correct Maven dependencies and by adding the necessary Spring configuration beans. To make it easy an example application has been created, named activiti-app-embedded-example. If you don’t have this example project as part of the Activiti app download, you can ask for a copy with your SkyVault account or sales representative. The Maven pom.xml file in this example project can be used to get an overview of all necessary Maven dependencies. The example project also contains the Spring configuration beans that are needed by the Activiti app components.
The src/main/webapp folder contains all the Javascript sources of the Activiti app in minified format. As a customer you can have access to the full Javascript source as well, but that’s provided in a separate bundle. If the context root of the application is changed be sure to change the URI configuration in the app-cfg.js file in the src/main/webapp/scripts folder.
20. Start and task form customisation
The start and task forms that are part of a task view can be customised for specific requirements. The following Javascript code example provides an overview of all the form and form field events that can be used to implement custom logic.
By default, a file name render-form-extensions.js in the workflow/extensions folder is present and loaded in the index.html file of the workflow folder. It has empty methods by default:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145 var SkyVault = SkyVault || {};
SkyVault.formExtensions = {
// This method is invoked when the form field have been rendered
formRendered:function(form, scope) {
},
// This method is invoked when input values change (ng-change function)
formFieldValueChanged:function(form, field, scope) {
},
// This method is invoked when an input field gets focus (focus event with ng-focus function)
formFieldFocus:function(form, field, scope) {
},
// This method is invoked when an input field has lost focus (blur event with ng-blur function)
formFieldBlur:function(form, field, scope) {
},
// This method is invoked when a person has been selected in the people picker
formFieldPersonSelected:function(form, field, scope) {
},
// This method is invoked when an email has been filled-in in the people picker
formFieldPersonEmailSelected:function(form, field, scope) {
},
// This method is invoked when a person has been removed in the people picker
formFieldPersonRemoved:function(form, field, scope) {
},
// This method is invoked when a group has been selected in the functional group picker
formFieldGroupSelected:function(form, field, scope) {
},
// This method is invoked when a group has been removed in the functional group picker
formFieldGroupRemoved:function(form, field, scope) {
},
// This method is invoked when content has been uploaded in the upload field
formFieldContentUploaded:function(form, field, scope) {
},
// This method is invoked when content has been removed in the upload field
formFieldContentRemoved:function(form, field, scope) {
},
// This method is invoked when the REST values or set in a dropdown, radio or typeahead field
formFieldRestValuesSet:function(form, field, scope) {
},
// This method is invoked when the complete or an outcome button has been clicked and before the task is completed.
formBeforeComplete:function(form, outcome, scope) {
},
// This method is invoked when input values change (ng-change function) in a dynamic table
formTableFieldValueChanged:function(form, field, columnDefinition, editRow, scope) {
},
// This method is invoked when an input field gets focus (focus event with ng-focus function) in a dynamic table
formTableFieldFocus:function(form, field, columnDefinition, editRow, scope) {
},
// This method is invoked when an input field has lost focus (blur event with ng-blur function) in a dynamic table
formTableFieldBlur:function(form, field, columnDefinition, editRow, scope) {
},
// This method is invoked when the REST values or set in a dropdown field in a dynamic table
formTableFieldRestValuesSet:function(form, field, columnDefinition, editRow, scope) {
},
// This method is invoked when the form fields have been rendered in the dynamic table popup
formTableRendered:function(form, field, columnDefinitions, editRow, scope) {
},
// This method is invoked when the complete button has been clicked and before the dynamic table popup is completed.
formTableBeforeComplete:function(form, field, editRow, scope) {
},
// This method is invoked when the cancel button has been clicked and before the dynamic table popup is cancelled.
formTableBeforeCancel:function(form, field, editRow, scope) {
},
// This method is invoked when input values change (ng-change function) and will disable the complete buttons when false (boolean) is returned.
formValidateFieldValueChanged:function(form, field, scope) {
},
// This method is invoked when the complete button has been clicked and will prevent the form completion when false (boolean) is returned.
formValidateBeforeSubmit:function(form, outcome, scope) {
},
// This method is invoked when input values change (ng-change function) in a dynamic table and will disable the save button when false (boolean) is returned.
formTableValidateFieldValueChanged:function(form, field, columnDefinition, editRow, scope) {
},
// This method is invoked when the complete button has been clicked and before the dynamic table popup is completed and prevent the form completion
// when false (boolean) is returned.
formTableValidateBeforeComplete:function(form, field, editRow, scope) {
},
// This method is invoked when a task is completed successfully
taskCompleted:function(taskId, form, scope) {
},
// This method is invoked when a task is completed unsuccessfully
taskCompletedError:function(taskId, errorResponse, form, scope) {
},
// This method is invoked when a task is saved successfully
taskSaved:function(taskId, form, scope) {
},
// This method is invoked when a task is saved unsuccessfully
taskSavedError:function(taskId, errorResponse, form, scope) {
}
};
This file can be changed to add custom logic. Alternatively, it is of course possible to add new javascript files and reference them in the index.html file. Do take those files in account when upgrading to newer versions of the application.
In every event method the full form variable is passed as a parameter. This form variable contains the form identifier and name, but also the full set of form fields with type and other configuration information.
In addition the changed field is passed when applicable and the Angular scope of the form renderer is also included. This is a regular Angular directive (i.e. isolated) scope, with all methods available.
For example, to get the current user:
1
2
3
4 formRendered:function(form, scope) {
var currentUser = scope.$root.account;
console.log(currentUser);
}
21. Custom form fields
Custom form field types can be added through custom form stencils. A form stencil is based on the default form stencil and can have default form field types removed, reordered, tweaked (changing the name, icon, etc.) or have new form field types.
Form stencils are defined in the Stencils section of the Kickstart App. A new form field type consists of the following:
-
An html template that is rendered when drag and dropping from the palette on the form canvas is the form builder.
-
An html template that is rendered when the form is displayed at runtime.
-
An optional custom AngularJS controller in case custom logic needs to be applied to the form field.
-
An optional list of third party scripts that are needed when working with the form field at runtime.
21.1. Example 1: Static image
This is a very basic example of a custom form field type that simply displays a static image.
Create a new form stencil in the Kickstart App and click the Add new item link.
The Form runtime template (the html used when the form is rendered at runtime) and the Form editor template (the html used in the form builder) is the same here:
1 <img src="http://activiti.org/images/activiti_logo.png"></img>
21.2. Example 2: Dynamic image
Create another new item for the form stencil. This time, we’ll create a configurable image. So unlike the static image of the previous example, here the user building the form will be able to select the image that will be displayed.
The Form runtime template needs to show the image that the form builder has selected. We’ll assume we have set a property url (see later on). Note how we’re using ng-src here (see AngularJs docs on ng-src) to have a dynamic image:
1 <img ng-src="{{field.params.customProperties.url}}"></img>
Note the syntax field.params.customProperties to get access to the non-default properties of the the form field.
The Form editor template simply needs to be a generic depiction of an image or even simpler like here, just a bit of text
1 <i>The custom image here</i>
Don’t forget to add a property url to this stencil item with the name url and type text.
21.3. Example 3: Dynamic pie chart
This example is more advanced then the previous two: here, we’ll have a simple list of number fields with a button at the bottom to add a new line item, while generating a pie chart on the right.
We’ll use the 'Epoch' library as an example here. Download the following files from its Github site:
Create a new form stencil item and name it "Chart". Scroll down towards the Script library imports section, and upload these two libraries. At runtime, these third party libraries will be included when the form is rendered.
Note: the order in which the third party libraries are defined is important. Since the Epoch library depends on d3, d3 needs to be first in the table and epoch second (as that is the order in which they are loaded at runtime).
The Form editor template is the easy part. We could just use an image of a pie chart here.
1 <img src="url_to_pie_chart_image.png"></img>
Let’s first define the controller for this form field type. The controller is an AngularJs controller, that will do mainly three things:
-
Keep a model of the line items
-
Implement a callback for the button that can be clicked
-
Store the value of the form field in the proper format of Activiti
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91 angular.module('activitiApp')
.controller('MyController', ['$rootScope', '$scope', function ($rootScope, $scope) {
console.log('MyController instantiated');
// Items are empty on initialisation
$scope.items = [];
// The variable to store the piechart data (non angular)
var pieChart;
// Epoch can't use the Angular model, so we need to clean it
// (remove hashkey etc, specific to Angular)
var cleanItems = function(items) {
var cleanedItems = [];
items.forEach(function(item) {
cleanedItems.push( { label: item.label, value: item.value} );
});
return cleanedItems;
};
// Callback for the button
$scope.addItem = function() {
// Update the model
$scope.items.push({ label: 'label ' + ($scope.items.length + 1), value: 0 });
// Update the values for the pie chart
// Note: Epoch is not an angular lib so doesn't use the model directly
if (pieChart === undefined) {
pieChart = jQuery('.activiti-chart-' + $scope.field.id).epoch({
type: 'pie',
data: cleanItems($scope.items)
});
console.log('PieChart created');
} else {
$scope.refreshChart();
}
};
// Callback when model value changes
$scope.refreshChart = function() {
pieChart.update(cleanItems($scope.items));
console.log('PieChart updated');
};
// Register this controller to listen to the form extensions methods
$scope.registerCustomFieldListener(this);
// Deregister on form destroy
$scope.$on("$destroy", function handleDestroyEvent() {
console.log("destroy event");
$scope.removeCustomFieldListener(this);
});
// Setting the value before completing the task so it's properly stored
this.formBeforeComplete = function(form, outcome, scope) {
console.log('Before form complete');
$scope.field.value = JSON.stringify(cleanItems($scope.items));
};
// Needed when the completed form is rendered
this.formRendered = function(form, scope) {
console.log(form);
form.fields.forEach(function(field) {
if (field.type === 'readonly'
&& $scope.field.id == field.id
&& field.value
&& field.value.length > 0) {
$scope.items = JSON.parse(field.value);
$scope.isDisabled = true;
pieChart = jQuery('.activiti-chart-' + $scope.field.id).epoch({
type: 'pie',
data: cleanItems($scope.items)
});
}
});
};
}]);
The Form runtime template needs to reference this controller, use the model and link the callback for the button:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/epoch/0.6.0/epoch.min.css">
<div ng-controller="MyController" style="float:left;margin: 35px 20px 0 0;">
<div ng-repeat="item in items">
<input type="text" ng-model="item.label" style="width:200px; margin: 0 10px 10px 0;" ng-change="refreshChart()">
<input type="number" ng-model="item.value" style="width: 80px; margin-bottom: 10px;" ng-change="refreshChart()">
</div>
<div>
<button class="btn btn-default btn-sm" ng-click="addItem()" ng-disabled="isDisabled">
Add item
</button>
</div>
</div>
<div class="epoch category10" ng-class="'activiti-chart-' + field.id" style="display:inline-block;width: 200px; height: 200px;"></div>
<div class="clearfix"></div>
At runtime, the following will be rendered:
22. Document Templates
The document generation task/step uses a document template to generate a PDF or Microsoft Word document, based on a Word document template (.docx) where process variables can be injected.
Such a document template can be
-
Tenant wide: everybody can use the template in their processes. Useful for 'company' templates
-
Process model specific: the template is uploaded whilst modeling the process model, and is bound to the lifeycle of the process model
When exporting an App model, process model document templates will be included (and will also be uploaded again on import). Tenant document templates are not exported, but matched on the document template name (names are unique for tenant document templates).
In the .docx template, process variables can be inject using following syntax:
<<[myVariable]>>
This way of injecting variables is the easiest, but does not do any null checks (an exception will happen at runtime if null). A more advanced version looks like:
<<[variables.get("myVariable")]>>
If this variable is null, a default value will be injected instead. A default value can be provided too:
<<[variables.get("myVariable", "myDefaultValue")]>>
Note: certain form field types (like the dropdown field type) have an id and label value. The id is the technical value, used by service tasks, etc, and will be injected by default. If you want the label value to show up in the generated document (like regular people usually do), use myVariable_LABEL.
Under the hood the document generation is done using the Aspose library. More information about the template syntax and possibilities can be found in the Aspose documentation.
The audit log is also generated the same way. This is a snippet from the template, showing some more advanced constructs:
It is also possible to have custom Spring bean that processes the process variables just before rendering the document.
23. Integration With External Systems
23.1. SkyVault 2.0
The SkyVault 2.0 (on premises) integration can be used to: * Upload or link related content (e.g. for a task) * Upload or link content in a form
The connection for a SkyVault installation is created by an administrator through the user interface. Accounts for connecting to a SkyVault installation are created by the user themself.
Passwords are stored encrypted in the database. An init vector and secret key are used for the encryption. These keys can be changed from the default values as follows:
1
2
3
4
5 # Passwords for non-OAuth services (eg. on-premise SkyVault) are encrypted using AES/CBC/PKCS5PADDING
# It needs a 128-bit initialization vector (http://en.wikipedia.org/wiki/Initialization_vector) and a 128-bit secret key
# represented as 16 ascii characters below
security.useraccount.credentialsIVSpec=9kje56fqwX8lk1Z0
security.useraccount.credentialsSecretSpec=wTy53pl09aN4iOkL
23.2. SkyVault Cloud
The SkyVault Cloud integration can be used to: * Upload or link related content (eg. for a task) * Upload or link content in a form
To integrate with the SkyVault Cloud, an account is needed that provides API access. Such an account can be created here.
The following properties need to be set. Note that the redirectUri must match the host on which the SkyVault Activiti BPM Suite is running. The app/rest/integration/alfresco-cloud/confirm-auth-request can be copied as-is.
1
2
3 SkyVault.cloud.clientId=abc
SkyVault.cloud.secret=abc
SkyVault.cloud.redirectUri=http://localhost:8080/activiti-app/app/rest/integration/alfresco-cloud/confirm-auth-request
It’s possible to disable the SkyVault Cloud support so that it won’t show up in the upload widget. By default it’s enabled.
1 SkyVault.cloud.disabled=true
23.3. Google Drive
The Google Drive integration can be used to:
-
Upload related content (eg. for a task)
-
Upload content in a form
The Google Drive integration needs a valid development account to access the API. Also, see this link for more information.
Such an account also has a secret, x509 certificate url and client id. These settings are provided by the Google Drive Dev Account.
1
2
3
4
5
6
7
8
9
10
11
12
13
14 # No need to change these properties
googledrive.web.auth_uri=https://accounts.google.com/o/oauth2/auth
googledrive.web.token_uri=https://accounts.google.com/o/oauth2/token
googledrive.web.auth_provider_x509_cert_url=https://www.googleapis.com/oauth2/v1/certs
# Following properties need to be changed to map to the correct url
googledrive.web.redirect_uris=http://localhost:8080/activiti-app/app/rest/integration/google-drive/confirm-auth-request
googledrive.web.javascript_origins=http://localhost:8080/activiti-app
# Following properties are provided by Google
googledrive.web.client_secret=aabbcc
googledrive.web.client_email=bla
googledrive.web.client_x509_cert_url=bla
googledrive.web.client_id=bla
It’s possible to disable the Google Drive support so that it won’t show up in the upload widget. By default it’s enabled.
1 googledrive.web.disabled=true
24. Custom Logic
Custom logic in a business process is often implemented using a JavaDelegate implementation or a Spring bean. Please see the Activiti Engine User Guide (http://activiti.org/userguide/index.html) for more information on this topic.
To build against a specific version of the SkyVault Activiti BPM Suite, add following dependency to your Maven pom.xml file:
1
2
3
4
5
6
7 <dependencies>
<dependency>
<groupId>com.activiti</groupId>
<artifactId>activiti-app-logic</artifactId>
<version>${suite.version}</version>
</dependency>
</dependencies>
24.1. Java Delegates
The simplest option is to create a class that implements the org.activiti.engine.delegate.JavaDelegate interface, like this:
1
2
3
4
5
6
7
8
9
10
11
12
13 package my.company;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
public class MyJavaDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
System.out.println("Hello from the class delegate");
execution.setVariable("var1", "Hello from the class delegate");
}
}
Build a jar with this class, and add it to the classpath. In the Service task configuration, set the 'class' property to using the fully qualified classname (in this case my.company.MyJavaDelegate).
24.2. Spring Beans
Another option is to use a Spring bean. It is possible to use a delegateExpression on a service task that resolves at runtime to an instance of org.activiti.engine.delegate.JavaDelegate. Alternatively, and probably more useful, is to use a general Spring bean. The application automatically scans all beans in the com.activiti.extension.bean package. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package com.activiti.extension.bean;
import org.activiti.engine.impl.pvm.delegate.ActivityExecution;
import org.springframework.stereotype.Component;
@Component("helloWorldBean")
public class HelloWorldBean {
public void sayHello(ActivityExecution execution) {
System.out.println("Hello from " + this);
execution.setVariable("var3", " from the bean");
}
}
Build a jar with this class, and add it to the classpath. To use this bean in a service task, set the expression property to ${helloWorldBean.sayHello(execution)}.
It is possible to define custom configuration classes (using the Spring Java Config approach) if this is needed (for example when sharing dependencies between delegate beans, complex bean setup, etc.). The application automatically scans for configuration classes in the package com.activiti.extension.conf; package. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.activiti.extension.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomConfiguration {
@Bean
public SomeBean someBean() {
return new SomeBean();
}
}
Which can be injected in the bean that will be called in a service task:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.activiti.extension.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.activiti.extension.conf.SomeBean;
@Component("helloWorldBeanWithInjection")
public class HelloWorldBeanWithInjection {
@Autowired
private SomeBean someBean;
public void sayHello() {
System.out.println(someBean.getValue());
}
}
To get the current user, it is possible to use the com.activiti.common.security.SecurityUtils helper class.
24.3. Default Spring Beans
The following beans are available out of the box in the Activiti BPM Suite:
24.3.1. Audit Log Bean ("auditLogBean")
The auditLogBean can be used to generate audit logs in .pdf format for a process instance or a task. The log will be saved as a field value to the process (and the task if a task audit log is generated).
The following code can be used in the expression of a service task to generate a process instance audit log named 'My first process instance audit log'. The third argument determines if the current date shall be appended to the file name. The pdf will be associated with the process field 'myFieldName'.
${auditLogBean.generateProcessInstancePdf(execution, 'My first process instance audit log', true, 'myFieldName')}
To create a task audit log named 'My first task audit log' add the following expression to the "complete" event in a task listener. Again the third argument determines if the current date shall be appended to the file name. The pdf will be associated with the field 'myFieldName'.
${auditLogBean.generateTaskPdf(task, 'My first task audit log', true, 'myFieldName')}
It is also possible to view the audit logs from within the My Tasks app by clicking the "Audit Log" link when viewing the details of a completed process or task. When doing so the following 2 rest calls are made.
Process instance audit log:
GET app/rest/process-instances/{process-instance-id}/audit
Task audit log:
GET app/rest/tasks/{task-id}/audit
24.3.2. Document Merge Bean ("documentMergeBean")
The documentMergeBean can be used to merge the content of multiple documents (files of type .doc or .docx) from a process into a single document which will be become the value of a provided process variable. The filename of the new document will be set to the filename of the first field in the list followed by the string "_merged" and the suffix from the same field.
In the following example the content of 'myFirstField' and 'mySecondField' will be merged into a new document with the field id set to 'myFirstField' and the filename set to: "<filename-from-myFirstField>_merged.<filenameSuffix-from-myFirstFields>". The new document will become the value of a process variable named 'myProcessVariable'.
${documentMergeBean.mergeDocuments('myFirstField,mySecondField', 'myProcessVariable', execution)}
24.3.3. Email Bean ("emailBean")
The emailBean can be used to retrieve the email of the current user or the process initiatior.
To get the email of the current user use the following expression where 123 is the userId:
${emailBean.getEmail(123)}
To get the email of the process initiatior use the following expression:
${emailBean.getProcessInitiator(execution)}
24.3.4. User Info Bean ("userInfoBean")
The userInfoBean makes it possible to get access to general information about a user or just the email of a user.
To get general information about a user (the data that can be found in com.activiti.domain.idm.User) use the following expression where userId is the database id of the user and can be supplied either as a Long or a String.
${userInfoBean.getUser(123)}
To get the email of a user use the following expression where 123 is the database id of the user and can be supplied either as a Long or a String.
${userInfoBean.getEmail(123)}
24.4. Hook points
A hook point is a place where custom logic can be added. Typically this is done by implementing a certain interface and putting the class implementing the interface on the classpath where it can be found by the classpath component scanning (package com.activiti.extension.bean for example)..
24.4.1. Login/LogoutListener
interface: com.activiti.api.security.LoginListener and com.activiti.api.security.LogoutListener
Maven module: activiti-app-logic
An implementation of this class will get a callback when a user logs in or logs out.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.activiti.extension.bean;
@Component
public class MyLoginListener implements LoginListener {
private static final Logger logger = LoggerFactory.getLogger(GfkLoginListener.class);
public void onLogin(User user) {
logger.info("User " + user.getFullName() + " has logged in");
}
}
24.4.2. Process engine configuration configurer
interface: com.activiti.api.engine.ProcessEngineConfigurationConfigurer
Maven module: activiti-app-logic
An implementation of this class will get called when the Activiti process engine configuration is initialized, but before the process engine is built. This allows for customization to the process engine configuration.
Example:
1
2
3
4
5
6
7
8 @Component
public class MyProcessEngineCfgConfigurer implements ProcessEngineConfigurationConfigurer {
public void processEngineConfigurationInitialized( SpringProcessEngineConfiguration springProcessEngineConfiguration) {
... // Tweaking the process engine configuration
}
}
24.4.3. Document generation variables processing
interface: com.activiti.api.docgen.TemplateVariableProcessor
Maven module: activiti-app-logic
This is the context of the 'document generation' task (generating a document based on a MS Word docx template).
An implementation of this class will get called before the variable is passed to the template processor, making it possible to change the value that will be used in the template where the variable name is used.
Example:
1
2
3
4
5
6
7
8 @Component
public class MyTemplateVariableProcessor implements TemplateVariableProcessor {
public Object process(org.activiti.engine.delegate.DelegateExecution execution, String variableName, Object value) {
return value.toString() + "___" + "HELLO_WORLD";
}
}
This example implementation very simplistically adds "HELLO_WORLD" to all variable usages in the template. Of course, smarter implementations based on process definition lookup, etc. are possible.
24.5. Custom Rest endpoints
It’s possible to add custom REST endpoints to the BPM Suite, both in the regular REST API (used by the BPM Suite html/javascript UI) and the public API (using basic authentication instead of cookies).
The REST API in the SkyVault Activiti BPM Suite is built using Spring MVC. Please check the Spring MVC documentation on how to create new Java beans to implement REST endpoints.
To build against the REST logic of the SkyVault Activiti BPM Suite and its specific dependencies, add following dependency to your Maven pom.xml file:
1
2
3
4
5
6
7 <dependencies>
<dependency>
<groupId>com.activiti</groupId>
<artifactId>activiti-app-rest</artifactId>
<version>${suite.version}</version>
</dependency>
</dependencies>
The bean needs to be in the com.activiti.extension.rest package to be found!
A very simple example is shown below. Here, the Activiti TaskService is injected and a custom response is fabricated. Of course, this logic can be anything.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 package com.activiti.extension.rest;
import com.activiti.domain.idm.User;
import com.activiti.security.SecurityUtils;
import org.activiti.engine.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/rest/my-rest-endpoint")
public class MyRestEndpoint {
@Autowired
private TaskService taskService;
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public MyRestEndpointResponse executeCustonLogic() {
User currentUser = SecurityUtils.getCurrentUserObject();
long taskCount = taskService.createTaskQuery().taskAssignee(String.valueOf(currentUser.getId())).count();
MyRestEndpointResponse myRestEndpointResponse = new MyRestEndpointResponse();
myRestEndpointResponse.setFullName(currentUser.getFullName());
myRestEndpointResponse.setTaskCount(taskCount);
return myRestEndpointResponse;
}
private static final class MyRestEndpointResponse {
private String fullName;
private long taskCount;
// Getters and setters
}
}
Create a jar containing this class, and add it to the SkyVault Activiti BPM Suite classpath.
A class like this in the com.activiti.extension.rest package will be added to the rest endpoints for the application (e.g. for use in the UI), which use the cookie approach to determine the user. The url will be mapped under /app. So, if logged in into the UI of the BPM Suite, one could go to http://localhost:8080/activiti-app/app/rest/my-rest-endpoint and see the result of the custom rest endpoint:
{"fullName":" Administrator","taskCount":8}
To add a custom REST endpoint to the public REST API, protected by basic authentication, a similar class should be placed in the com.activiti.extension.api package:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 package com.activiti.extension.api;
import com.activiti.domain.idm.User;
import com.activiti.security.SecurityUtils;
import org.activiti.engine.TaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/my-api-endpoint")
public class MyApiEndpoint {
@Autowired
private TaskService taskService;
@RequestMapping(method = RequestMethod.GET, produces = "application/json")
public MyRestEndpointResponse executeCustonLogic() {
User currentUser = SecurityUtils.getCurrentUserObject();
long taskCount = taskService.createTaskQuery().taskAssignee(String.valueOf(currentUser.getId())).count();
MyRestEndpointResponse myRestEndpointResponse = new MyRestEndpointResponse();
myRestEndpointResponse.setFullName(currentUser.getFullName());
myRestEndpointResponse.setTaskCount(taskCount);
return myRestEndpointResponse;
}
private static final class MyRestEndpointResponse {
private String fullName;
private long taskCount;
// Getters and setters
}
}
Which can be accessed like the regular API:
> curl -u admin@app.activiti.com:password http://localhost:8080/activiti-app/api/my-api-endpoint > {"fullName":" Administrator","taskCount":8}
Note: due to classloading, it is currently not possible to put jars with these custom rest endpoints in the global or common classpath (for example tomcat/lib for Tomcat). They should be put in the web application classpath (for example WEB-INF/lib).
25. Cookie configuration
The SkyVault Activiti BPM Suite uses an HTTP cookie to store a user session. Multiple cookies per uses (for different browsers or devices) are possible. The application uses a database table to store the cookie values (called tokens internally), to allow a shared persistent session store in a multi-node setup.
It’s possible to change the settings regarding cookies:
Property | description | default |
---|---|---|
security.cookie.max-age |
The maximum age of a cookie, expressed in seconds. The max-age determines the period in which the browser will send the cookie with the requests. |
2678400 (31 days) |
security.cookie.refresh-age |
To avoid that a users is suddenly logged out when using the application when reaching the max-age above, tokens are refreshed after this period (expressed in seconds). Refreshing means a new token will be created and a new cookie will be returned which the browser will use for subsequent requests. Setting the refresh-age low, will result in many new database rows when the user is using the application. |
86400 (1 day) |
By default, cookies will have the secure flag set, when the request being made is HTTPS. If you only want to use the remember-me cookie over HTTPS (i.e. make the secure flag mandatory), set the following property to true:
Property | default |
---|---|
security.cookie.always-secure |
false |
To avoid that the persistent token table gets too full, a background job periodically removes obsolete cookie token values. Possible settings:
Property | description | default |
---|---|---|
security.cookie.database-removal.max-age |
The maximum age an entry in the database needs to have to be removed. |
Falls back to the security.cookie.max-age setting if not found. This effectively means that cookies which are no longer valid could be removed immediately from the database table. |
security.cookie.database-removal.cronExpression |
The cron expression determining when the obsolete database table entries for the cookie values will checked for removal. |
0 0 1 * * ? (01:00 at night) |
26. REST API
The SkyVault Activiti BPM Suite comes with a REST API. It includes both the Activiti Open Source REST API exposing the generic Activiti Engine operations and a dedicated set op REST API endpoints specific for the functionality in the SkyVault Activiti BPM Suite.
Note that there is also an 'internal' REST API, which are the REST endpoints used by the Javascript UI. It is advised not to use this API, these REST API urls and way of using it will change and evolve with the product (and are undocumented). The 'official' API is considered to be stable. Also, the internal REST API uses a different authentication mechanism tailored towards web browser usage.
26.1. Authentication
The REST API uses Basic Authentication for user authentication. This means that every request needs to have the Authorization header appropriately set.
26.2. Activiti Open Source REST API
The Activiti open source REST API is bundled with the SkyVault Activiti BPM Suite. This means that all operations described in the Activiti User Guide are available as documented there, except for REST endpoints that don’t make sense within the Suite product (e.g. forms, as they are implemented differently).
This REST API is available on <your-server-and-context-root>/api/
For example: fetching process definitions is described in the Activiti User Guide as an HTTP GET on repository/process-definitions. This maps to <your-server-and-context-root>/api/repository/process-definitions.
Important: requests on this REST API can only be done using a user that is a tenant admin (responsible for one tenant) or a tenant manager (responsble for many tenants). This matches the Actviti Engine (Java) Api, which is agnostic of user permissions. This means that when calling any of the operations, the tenant identifier must always be provided in the url, even if the system does not have multi tenancy (there will always be one tenant in that case).
26.3. SkyVault Activiti BPM Suite API
This REST API exposes data and operations which are specific to the SkyVault Activiti BPM Suite. Contrary to the Activiti Open Source REST API it can be called using any user. The following sections describe the various REST API endpoints.
26.3.1. Server Information
To retrieve information about the Activiti BPM Suite version:
GET api/enterprise/app-version
Response:
1
2
3
4
5
6
7 {
"edition": "SkyVault Activiti Enterprise BPM Suite",
"majorVersion": "1",
"revisionVersion": "0",
"minorVersion": "2",
"type": "bpmSuite",
}
26.3.2. Profile
This operation returns account information for the current user. This is useful to get the name, email, the groups that the user is part of, the user picture, etc.
GET api/enterprise/profile
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 {
"tenantId": 1,
"firstName": "John",
"password": null,
"type": "enterprise",
"company": null,
"externalId": null,
"capabilities": null,
"tenantPictureId": null,
"created": "2015-01-08T13:22:36.198+0000",
"pictureId": null,
"latestSyncTimeStamp": null,
"tenantName": "test",
"lastName": "Doe",
"id": 1000,
"lastUpdate": "2015-01-08T13:34:22.273+0000",
"email": "johndoe@alfresco.com",
"status": "active",
"fullname": "John Doe",
"groups": [
{
"capabilities": null,
"name": "analytics-users",
"tenantId": 1,
"users": null,
"id": 1,
"groups": null,
"externalId": null,
"status": "active",
"lastSyncTimeStamp": null,
"type": 0,
"parentGroupId": null
},
{
"capabilities": null,
"name": "Engineering",
"tenantId": 1,
"users": null,
"id": 2000,
"groups": null,
"externalId": null,
"status": "active",
"lastSyncTimeStamp": null,
"type": 1,
"parentGroupId": null
},
{
"capabilities": null,
"name": "Marketing",
"tenantId": 1,
"users": null,
"id": 2001,
"groups": null,
"externalId": null,
"status": "active",
"lastSyncTimeStamp": null,
"type": 1,
"parentGroupId": null
}
]
}
To update user information (first name, last name or email):
POST api/enterprise/profile
The body of the request needs to be a json looking like
1
2
3
4
5
6 {
"firstName" : "John",
"lastName" : "Doe",
"email" : "john@alfresco.com",
"company" : "Alfresco"
}
To get the user picture, use following REST call:
GET api/enterprise/profile-picture
To change this picture, do an HTTP POST to the same url, with the picture as multipart file in the body.
Finally, to change the password:
POST api/enterprise/profile-password
with a json body that looks like
1
2
3
4 {
"oldPassword" : "12345",
"newPassword" : "6789"
}
26.3.3. Runtime Apps
When a user logs in into the SkyVault Activiti BPM Suite, the landing page is displayed containing all the apps that the user is allowed to see and use.
The corresponding REST API request to get this information is
GET api/enterprise/runtime-app-definitions
Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 {
"size": 3,
"total": 3,
"data": [
{
"deploymentId": "26",
"name": "HR processes",
"icon": "glyphicon-cloud",
"description": null,
"theme": "theme-6",
"modelId": 4,
"id": 1
},
{
"deploymentId": "2501",
"name": "Sales onboarding",
"icon": "glyphicon-asterisk",
"description": "",
"theme": "theme-1",
"modelId": 1002,
"id": 1000
},
{
"deploymentId": "5001",
"name": "Engineering app",
"icon": "glyphicon-asterisk",
"description": "",
"theme": "theme-1",
"modelId": 2001,
"id": 2000
}
],
"start": 0
}
The id and modelId property of the apps are important here, as they are used in various operations described below.
26.3.4. App Definitions List
To retrieve the app definitions (note that this means all app definitions, not only those deployed at runtime):
GET api/enterprise/models?filter=myApps&modelType=3&sort=modifiedDesc
The request parameters
-
filter : can be myApps, sharedWithMe, sharedWithOthers or favorite
-
modelType : must be 3 for app definition models
-
sort : modifiedDesc, modifiedAsc, nameAsc or nameDesc (default modifiedDesc)
26.3.5. App Import And Export
It is possible to export app definitions and import them again. From the REST API point of view, this is useful to bootstrap an environment (for users or continous integration).
To export an app definition, you need the modelId from a runtime app or the id of an app definition model, and call
GET api/enterprise/app-definitions/{modelId}/export
This will return a zip file containing the app definition model and all related models (process definitions and forms).
To import an app again, post the zip file as multipart file to
POST api/enterprise/app-definitions/import
To import an app to an existing app definition to create a new version instead of importing a new app definition, post the zip file as multipart file to
POST api/enterprise/app-definitions/{modelId}/import
26.3.6. App Publish and Deploy
Before an app model can be used, it need to be published. This can be done through following call:
POST api/enterprise/app-definitions/{modelId}/publish
A JSON body is required for the call. You can either use an empty one or one looking like
1
2
3
4 {
"comment": "",
"force": false
}
At this point, the user can add it to his/her landing page, by deploying the published app:
POST api/enterprise/runtime-app-definitions
with in the body one property appDefinitions which is an array of ids looking like
1
2
3 {
"appDefinitions" : [{"id" : 1}, {"id" : 2}]
}
26.3.7. Process Definition Models List
To retrieve a list of process definition models:
GET api/enterprise/models?filter=myprocesses&modelType=0&sort=modifiedDesc
The request parameters
-
filter : can be myprocesses, sharedWithMe, sharedWithOthers or favorite
-
modelType : must be 0 for process definition models
-
sort : modifiedDesc, modifiedAsc, nameAsc or nameDesc (default modifiedDesc)
26.3.8. Model Details and History
Both app definition and process definition models are versioned.
To retrieve details about a particular model:
GET api/enterprise/models/{modelId}
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 {
"createdBy": 1,
"lastUpdatedBy": 1,
"lastUpdatedByFullName": " Administrator",
"name": "aad",
"id": 2002,
"referenceId": null,
"favorite": false,
"modelType": 0,
"comment": "",
"version": 3,
"lastUpdated": "2015-01-10T16:24:27.893+0000",
"stencilSet": 0,
"description": "",
"createdByFullName": " Administrator",
"permission": "write",
"latestVersion": true
}
The response shows the current version of the model.
To retrieve a thumbnail of the model:
GET api/enterprise/models/{modelId}/thumbnail
To get the version information for a model:
GET api/enterprise/models/{modelId}/history
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 {
"size": 2,
"total": 2,
"data": [
{
"createdBy": 1,
"lastUpdatedBy": 1,
"lastUpdatedByFullName": " Administrator",
"name": "aad",
"id": 3000,
"referenceId": null,
"favorite": null,
"modelType": 0,
"comment": "",
"version": 2,
"lastUpdated": "2015-01-10T16:15:50.579+0000",
"stencilSet": 0,
"description": "",
"createdByFullName": " Administrator",
"permission": null,
"latestVersion": false
},
{
"createdBy": 1,
"lastUpdatedBy": 1,
"lastUpdatedByFullName": " Administrator",
"name": "aad",
"id": 2000,
"referenceId": null,
"favorite": null,
"modelType": 0,
"comment": null,
"version": 1,
"lastUpdated": "2015-01-10T16:07:41.831+0000",
"stencilSet": 0,
"description": "",
"createdByFullName": " Administrator",
"permission": null,
"latestVersion": false
}
],
"start": 0
}
To get a particular older version:
GET api/enterprise/models/{modelId}/history/{modelHistoryId}
26.3.9. BPMN 2.0 Import and Export
To export a process definition model to a BPMN 2.0 xml file:
GET api/enterprise/models/{processModelId}/bpmn20
For a previous version of the model:
GET api/enterprise/models/{processModelId}/history/{processModelHistoryId}/bpmn20
To import a BPMN 2.0 xml file:
POST api/enterprise/process-models/import
With the BPMN 2.0 xml file in the body as a multipart file.
26.3.10. Process Definitions
Get a list of process definitions (visible within the tenant of the user):
GET api/enterprise/process-definitions
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 {
"size": 5,
"total": 5,
"data": [
{
"id": "demoprocess:1:7504",
"name": "Demo process",
"description": null,
"key": "demoprocess",
"category": "http://www.activiti.org/test",
"version": 1,
"deploymentId": "7501",
"tenantId": "tenant_1",
"hasStartForm": true
},
...
],
"start": 0
}
Following parameters are possible
-
latest: a boolean value, indicating that only the latest versions of process definitions must be returned
-
appDefinitionId: when provided, only return process definitions belonging to a certain app
26.3.11. Start Form
When a process definitions has a start form (hasStartForm is true in the call above), the start form can be retrieved as follows:
GET api/enterprise/process-definitions/{process-definition-id}/start-form
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 {
"processDefinitionId": "p1:2:2504",
"processDefinitionName": "p1",
"processDefinitionKey": "p1",
"fields": [
{
"fieldType": "ContainerRepresentation",
"id": "container1",
"name": null,
"type": "container",
"value": null,
"required": false,
"readOnly": false,
"overrideId": false,
"placeholder": null,
"optionType": null,
"hasEmptyValue": null,
"options": null,
"restUrl": null,
"restIdProperty": null,
"restLabelProperty": null,
"layout": null,
"sizeX": 0,
"sizeY": 0,
"row": 0,
"col": 0,
"visibilityCondition": null,
"fields": {
"1": [
{
"fieldType": "FormFieldRepresentation",
"id": "label1",
"name": "Label1",
"type": "text",
"value": null,
"required": false,
"readOnly": false,
"overrideId": false,
"placeholder": null,
"optionType": null,
"hasEmptyValue": null,
"options": null,
"restUrl": null,
"restIdProperty": null,
"restLabelProperty": null,
"layout": {
"row": 0,
"column": 0,
"colspan": 1
},
"sizeX": 1,
"sizeY": 1,
"row": 0,
"col": 0,
"visibilityCondition": null
}
],
"2": [ ]
}
},
{
"fieldType": "DynamicTableRepresentation",
"id": "label21",
"name": "Label 21",
"type": "dynamic-table",
"value": null,
"required": false,
"readOnly": false,
"overrideId": false,
"placeholder": null,
"optionType": null,
"hasEmptyValue": null,
"options": null,
"restUrl": null,
"restIdProperty": null,
"restLabelProperty": null,
"layout": {
"row": 10,
"column": 0,
"colspan": 2
},
"sizeX": 2,
"sizeY": 2,
"row": 10,
"col": 0,
"visibilityCondition": null,
"columnDefinitions": [
{
"id": "p2",
"name": "c2",
"type": "String",
"value": null,
"optionType": null,
"options": null,
"restUrl": null,
"restIdProperty": null,
"restLabelProperty": null,
"required": true,
"editable": true,
"sortable": true,
"visible": true
}
]
}
],
"outcomes": [ ]
}
Note: to retrieve field values (eg. the typeahead field), following REST endpoint can be used:
GET api/enterprise/process-definitions/{processDefinitionId}/start-form-values/{field}
This returns a list of form values.
26.3.12. Start Process Instance
POST api/enterprise/process-instances
with a json body that contains following properties:
-
processDefinitionId : the process definition id
-
name: the name to give to the created process instance
-
values: this is a json object with the the form field id - formd field values. The id of the form field is retrieved from the start form call (see above).
-
outcome: if the start form has outcomes, this is one of those values
The response will contain the process instance details (including the id).
Once started, the completed form can be fetched using
GET /enterprise/process-instances/{processInstanceId}/start-form
26.3.13. Process Instance List
To get the list of process instances:
POST api/enterprise/process-instances/query
with a json body containing the query parameters. Following parameters are possible:
-
processDefinitionId
-
appDefinitionId
-
state (possible values are running, completed and all
-
sort (possible values are created-desc, created-asc, ended-desc, ended-asc)
-
page (for paging, default 0)
-
size (for paging, default 25)
Example response:
1
2
3
4
5
6
7
8
9 {
"size": 6,
"total": 6,
"start": 0,
"data":[
{"id": "2511", "name": "Test step - January 8th 2015", "businessKey": null, "processDefinitionId": "teststep:3:29",…},
...
]
}
26.3.14. Get Process Instance Details
GET api/enterprise/process-instances/{processInstanceId}
26.3.15. Delete a Process Instance
DELETE api/enterprise/process-instances/{processInstanceId}
26.3.16. Task List
POST api/enterprise/tasks/query
with a json body containing the query parameters. Following parameters are possible
-
appDefinitionId
-
processInstanceId
-
processDefinitionId
-
text (the task name will be filtered with this, using like semantics : %text%)
-
assignment
-
assignee : where the current user is the assignee
-
candidate: where the current user is a task candidate
-
group_x: where the task is assigned to a group where the current user is a member of. The groups can be fetched through the profile REST endpoint
-
no value: where the current user is involved
-
-
state (completed or active)
-
sort (possible values are created-desc, created-asc, due-desc, due-asc)
-
page (for paging, default 0)
-
size (for paging, default 25)
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 {
"size": 6,
"total": 6,
"start": 0,
"data":[
{
"id": "2524",
"name": "Task",
"description": null,
"category": null,
"assignee":{"id": 1, "firstName": null, "lastName": "Administrator", "email": "admin@app.activiti.com"…},
"created": "2015-01-08T10:58:37.193+0000",
"dueDate": null,
"endDate": null,
"duration": null,
"priority": 50,
"processInstanceId": "2511",
"processDefinitionId": "teststep:3:29",
"processDefinitionName": "Test step",
"processDefinitionDescription": null,
"processDefinitionKey": "teststep",
"processDefinitionCategory": "http://www.activiti.org/test",
"processDefinitionVersion": 3,
"processDefinitionDeploymentId": "26",
"formKey": "5"
}
,...
]
}
26.3.17. Task Details
GET api/enterprise/tasks/{taskId}
Response is similar to the list response.
26.3.18. Task Form
GET api/enterprise/task-forms/{taskId}
The response is similar to the response from the start form (see above).
Form field values that are populated through a REST backend, can be retrieved using
GET api/enterprise/task-forms/{taskId}/form-values/{field}
Which returns a list of form field values
26.3.19. Completing a Task Form
POST api/enterprise/task-forms/{taskId}
with a json body that contains
-
values: this is a json object with the the form field id - formd field values. The id of the form field is retrieved from the start form call (see above).
-
outcome: if the start form has outcomes, this is one of those values
26.3.20. Create a Standalone Task
To create a task (for the user in the authentication credentials) that is not associated with a process instance:
POST api/enterprise/tasks
with a json body that contains following properties:
-
name
-
description
26.3.21. Task Actions
To update the details of a task:
PUT api/enterprise/task/{taskId}
with a json body that can contain name, description and dueDate (ISO 8601 string)
For example:
Example response:
1
2
3
4
5 {
"name" : "name-updated",
"description" : "description-updated",
"dueDate" : "2015-01-11T22:59:59.000Z"
}
To complete a task (standalone or without a task form) (note: no json body needed!) :
PUT api/enterprise/tasks/{taskId}/action/complete
To claim a task (in case the task is assigned to a group):
PUT api/enterprise/tasks/{taskId}/action/claim
No json body needed. The task will be claimed by the user in the authentication credentials.
To assign a task to a user:
PUT api/enterprise/tasks/{taskId}/action/assign
with a json body that contains the assignee property which has as value the id of a user.
To involve a user with a task:
PUT api/enterprise/tasks/{taskId}/action/involve
with a json body that contains the userId property which has as value the id of a user.
To remove an involved user from a task:
PUT api/enterprise/tasks/{taskId}/action/remove-involved
with a json body that contains the userId property which has as value the id of a user.
To attach a form to a task:
PUT api/enterprise/tasks/{taskId}/action/attach-form
with a json body that contains the formId property which has as value the id of a form.
To attach a form to a task:
DELETE api/enterprise/tasks/{taskId}/action/remove-form
26.3.22. User Task Filters
Custom task queries can be saved as a user task filter. To get the list of task filters for the authenticated user:
GET api/enterprise/filters/tasks
with an option request parameter appId to limit the results to a specific app.
To get a specific user task filter:
GET api/enterprise/filters/tasks/{userFilterId}
To create a new user task filter:
POST api/enterprise/filters/tasks
with a json body that contains following properties:
-
name : name of the filter
-
appId : app id where the filter can be used
-
icon : path of the icon image
-
filter
-
sort : created-desc, created-asc, due-desc or due-asc
-
state : open, completed
-
assignment : involved, assignee or candidate
-
To update a user task filter:
PUT api/enterprise/filters/tasks/{userFilterId}
with a json body that contains following properties:
-
name : name of the filter
-
appId : app id where the filter can be used
-
icon : path of the icon image
-
filter
-
sort : created-desc, created-asc, due-desc or due-asc
-
state : open, completed
-
assignment : involved, assignee or candidate
-
To delete a user task filter:
DELETE api/enterprise/filters/tasks/{userFilterId}
To order the list of user task filters:
PUT api/enterprise/filters/tasks
with a json body that contains following properties:
-
order : array of user task filter ids
-
appId : app id
To get a list of user process instance filters
GET api/enterprise/filters/processes
with an option request parameter appId to limit the results to a specific app.
To get a specific user process instance task filter
GET api/enterprise/filters/processes/{userFilterId}
To create a user process instance task filter
PUT api/enterprise/filters/processes
with a json body that contains following properties:
-
name : name of the filter
-
appId : app id where the filter can be used
-
icon : path of the icon image
-
filter
-
sort : created-desc, created-asc
-
state : running, completed or all
-
To update a user process instance task filter
PUT api/enterprise/filters/processes/{userFilterId}
with a json body that contains following properties:
-
name : name of the filter
-
appId : app id where the filter can be used
-
icon : path of the icon image
-
filter
-
sort : created-desc, created-asc
-
state : running, completed or all
-
To delete a user process instance task filter
DELETE api/enterprise/filters/processes/{userFilterId}
26.3.23. Comments
Comments can be added to a process instance or a task. To get the list of comments:
GET api/enterprise/process-instances/{processInstanceId}/comments
GET api/enterprise/tasks/{taskId}/comments
To create a comments:
POST api/enterprise/process-instances/{processInstanceId}/comments
POST api/enterprise/tasks/{taskId}/comments
with in the json body one property called message, with a value that is the comment text.
26.3.24. Checklists
Checklists can be added to a task. To get a checklist:
GET api/enterprise/tasks/{taskId}/checklist
To create a checklist:
POST api/enterprise/tasks/{taskId}/checklist
Example request body:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 {
"name": "Task",
"description": null,
"category": null,
"assignee":{"id": 1, "firstName": null, "lastName": "Administrator", "email": "admin@app.activiti.com"…},
"created": "2015-01-08T10:58:37.193+0000",
"dueDate": null,
"endDate": null,
"duration": null,
"priority": 50,
"processInstanceId": "2511",
"processDefinitionId": "teststep:3:29",
"processDefinitionName": "Test step",
"processDefinitionDescription": null,
"processDefinitionKey": "teststep",
"processDefinitionCategory": "http://www.activiti.org/test",
"processDefinitionVersion": 3,
"processDefinitionDeploymentId": "26",
"formKey": "5"
}
To change the order of the items on a checklist:
PUT api/enterprise/tasks/{taskId}/checklist
with a json body that contains an ordered list of checklist items ids:
-
order : array of checklist item ids
26.3.25. User and Group lists
A common use case is that a user wants to select another user (eg. when assigning a task) or group.
Users can be retrieved with
GET api/enterprise/users
with following parameters
-
filter: to filter on the user first and last name
-
email: to retrieve users by email
-
externalId: to retieve users using the external id (set by the LDAP sync, if used)
-
excludeTaskId: filters out users already part of this task
-
excludeProcessId: filters out users already part of this process instance
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 {
"size": 2,
"total": 2,
"start": 0,
"data": [
{
"id": 1,
"firstName": null,
"lastName": "Administrator",
"email": "admin@app.activiti.com"
},
{
"id": 1000,
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@alfresco.com"
}
]
}
To retrieve a picture of a user:
GET api/enterprise/users/{userId}/picture
Groups can be retrieved with
GET api/enterprise/groups
with optional parameter filter that filters on group name.
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 {
"size": 2,
"total": 2,
"data": [
{
"externalId": null,
"name": "Engineering",
"id": 2000
},
{
"externalId": null,
"name": "Marketing",
"id": 2001
}
],
"start": 0
}
Get the users for a given group:
GET api/enterprise/groups/{groupId}/users
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 {
"size": 3,
"total": 3,
"data": [
{
"email": "john@alfresco.com",
"lastName": "Test",
"firstName": "John",
"id": 10
},
{
"email": "mary@alfresco.com",
"lastName": "Test",
"firstName": "Mary",
"id": 8
},
{
"email": "patrick@alfresco.com",
"lastName": "Test",
"firstName": "Patrick",
"id": 9
}
],
"start": 0
}
with a json body that contains following properties:
-
order : array of user task filter ids ==== Content
Content (documents and other files) can be attached to process instances and tasks.
To retrieve which content is attached to a process instance:
GET api/enterprise/process-instances/{processInstanceId}/content
likewise, for a task:
GET api/enterprise/tasks/{taskId}/content
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 {
"size": 5,
"total": 5,
"start": 0,
"data": [
{
"id": 4000,
"name": "tasks.PNG",
"created": "2015-01-01T01:01:01.000+0000",
"createdBy": {
"id": 1,
"firstName": "null",
"lastName": "Admin",
"email": "admin@app.activiti.com",
"pictureId": 5
},
"contentAvailable": true,
"link": false,
"mimeType": "image/png",
"simpleType": "image",
"previewStatus": "queued",
"thumbnailStatus": "queued"
}
,...
]
}
To get content metadata:
GET api/enterprise/content/{contentId}
To delete content:
DELETE api/enterprise/content/{contentId}
To get the actual bytes for content:
GET api/enterprise/content/{contentId}/raw
To upload content to a process instance:
POST api/enterprise/process-instances/{processInstanceId}/raw-content
where the body contains a multipart file.
To upload content to a task:
POST api/enterprise/process-instances/{taskId}/raw-content
where the body contains a multipart file.
To relate content (eg from SkyVault) to a process instance:
POST api/enterprise/process-instances/{processInstanceId}/content
where the json body contains following properties:
-
name
-
link (boolean)
-
source
-
sourceId
-
mimeType
-
linkUrl
Example body (from SkyVault OnPremise):
1
2
3
4
5
6
7 {
"name":"Image.png",
"link":true,
"source":"alfresco-1",
"sourceId":"30358280-88de-436e-9d4d-8baa9dc44f17@swsdp",
"mimeType":"image/png"
}
To upload content for a task:
POST api/enterprise/process-instances/{taskId}/content
where the json body contains following properties:
-
name
-
link (boolean)
-
source
-
sourceId
-
mimeType
-
linkUrl
In case of a start form with content fields, there is no task or process instance to related to. Following REST endpoints can be used:
POST api/enterprise/content/raw
26.3.26. Thumbnails
To retrieve the thumbnail of a certain piece of content:
GET api/enterprise/content/{contentId}/rendition/thumbnail
26.3.27. Identity Management
These are operations to manage tenants, groups and users. This is useful for example to bootstrap environments with the correct identity data.
Tenants
Following REST endpoints are only available for users that are either a tenand admin or a tenant manager. The tenant capability also depends for some operations on the type of license (multi-tenant license or not).
Get all tenants (tenant manager only):
GET api/enterprise/admin/tenants
Create a new tenant (tenant manager only):
POST api/enterprise/admin/tenants
the json body of this post contains two properties: name and active (boolean).
Update a tenant:
PUT api/enterprise/admin/tenants/{tenantId}
the json body of this post contains two properties: name and active (boolean).
Get tenant details:
GET api/enterprise/admin/tenants/{tenantId}
Delete a tenant
DELETE api/enterprise/admin/tenants/{tenantId}
Get tenant events:
GET api/enterprise/admin/tenants/{tenantId}/events
Get tenant logo:
GET api/enterprise/admin/tenants/{tenantId}/logo
Change tenant logo:
POST api/enterprise/admin/tenants/{tenantId}/logo
where the body is a multi part file.
Users
Following REST endpoints are only available for users that are either a tenand admin or a tenant manager.
Get a list of users:
GET api/enterprise/admin/users
with parameters
-
filter : name filter
-
status : possible values are pending, inactive, active, deleted
-
sort : possible values are createdAsc, createdDesc, emailAsc or emailDesc (default createdAsc)
-
page : for paging.
-
size : for paging
Create a new user
POST api/enterprise/admin/users
with a json body that needs to have following properties:
-
email
-
firstName
-
lastName
-
password
-
status (possible values are pending, inactive, active, deleted)
-
type (enterprise or trial. Best to set this to enterprise)
-
tenantId
Update user details:
PUT api/enterprise/admin/users/{userId}
with a json body containing email, firstName and lastName
Update user password:
PUT api/enterprise/admin/users
with a json body like
1
2
3
4 {
"users" : [1098, 2045, 3049]
"password" : "123"
}
Note that the users property is an array of user ids. This allows for bulk changes.
Update user status:
PUT api/enterprise/admin/users
with a json body like
1
2
3
4 {
"users" : [1098, 2045, 3049]
"status" : "inactive"
}
Note that the users property is an array of user ids. This allows for bulk changes.
Update user tenant id (only possible for _tenant manager):
PUT api/enterprise/admin/users
with a json body like
1
2
3
4 {
"users" : [1098, 2045, 3049]
"tenantId" : 1073
}
Note that the users property is an array of user ids. This allows for bulk changes.
Groups
Following REST endpoints are only available for users that are either a tenand admin or a tenant manager.
Internally, there are two types of groups: * functional groups: these map to organisational units * system groups: these are used to give users capabilities (a capability is assigned to a group, and every member gets the capability)
Get all groups:
GET api/enterprise/admin/groups
Optional parameters: * tenantId : only relevant for tenant manager user * functional (boolean): only return functional groups if true
Get group details:
GET api/enterprise/admin/groups/{groupId}
Example response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 {
"capabilities": [{
"name": "access-reports",
"id": 1
}],
"name": "analytics-users",
"tenantId": 1,
"users": [
{
"tenantId": 1,
"firstName": null,
"password": null,
"type": "enterprise",
"company": null,
"externalId": null,
"capabilities": null,
"tenantPictureId": null,
"created": "2015-01-08T08:30:25.164+0000",
"pictureId": null,
"latestSyncTimeStamp": null,
"tenantName": null,
"lastName": "Administrator",
"id": 1,
"lastUpdate": "2015-01-08T08:30:25.164+0000",
"email": "admin@app.activiti.com",
"fullname": " Administrator",
"groups": null
},
{
"tenantId": 1,
"firstName": "John",
"password": null,
"type": "enterprise",
"company": null,
"externalId": null,
"capabilities": null,
"tenantPictureId": null,
"created": "2015-01-08T13:22:36.198+0000",
"pictureId": null,
"latestSyncTimeStamp": null,
"tenantName": null,
"lastName": "Doe",
"id": 1000,
"lastUpdate": "2015-01-08T13:34:22.273+0000",
"email": "johndoe@alfresco.com",
"fullname": "John Doe",
"groups": null
}
],
"id": 1,
"groups": [],
"externalId": null,
"status": "active",
"lastSyncTimeStamp": null,
"type": 0,
"parentGroupId": null
}
Optional request arameter includeAllUsers (boolean value, by default true) to avoid getting all the users at once (not ideal if there are many users).
In that case, the following call can be used:
1 GET api/enterprise/admin/groups/{groupId}/users?page=2&pageSize=20
Create new group:
POST api/enterprise/admin/groups
where the json body contains following properties:
-
name
-
tenantId
-
type (0 for system group, 1 for functional group)
-
parentGroupId (only possible for functional groups. System groups can’t be nested)
Update a group:
PUT api/enterprise/admin/groups/{groupId}
Only the name property can be in the json body.
Delete a group:
DELETE api/enterprise/admin/groups/{groupId}
Add a user to a group:
POST api/enterprise/admin/groups/{groupId}/members/{userId}
Delete a user from a group:
DELETE api/enterprise/admin/groups/{groupId}/members/{userId}
Get the list of possible capabilities for a system group:
GET api/enterprise/admin/groups/{groupId}/potential-capabilities
Add a capability from previous list to the group:
POST api/enterprise/admin/groups/{groupId}/capabilities
where the json body contains one property capabilities that is an array of strings.
Remove a capability from a group:
DELETE api/enterprise/admin/groups/{groupId}/capabilities/{groupCapabilityId}
Alfreco repositories
A tenant administrator can configure one or more SkyVault repositories to use when working with content. To retrieve the SkyVault repositories configured for the tenant of the user used to do the request:
GET api/enterprise/profile/accounts/SkyVault
which returns something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 {
"size": 2,
"total": 2,
"data": [
{
"name": "TS",
"tenantId": 1,
"id": 1,
"accountUsername": "jbarrez",
"created": "2015-03-26T14:24:35.506+0000",
"shareUrl": "http://ts.alfresco.com/share",
"lastUpdated": "2015-03-26T15:37:21.174+0000",
"repositoryUrl": "http://ts.alfresco.com/alfresco",
"alfrescoTenantId": ""
},
{
"name": "TsTest",
"tenantId": 1,
"id": 1000,
"accountUsername": "jbarrez",
"created": "2015-03-26T15:37:36.448+0000",
"shareUrl": "http://tstest.alfresco.com/share",
"lastUpdated": "2015-03-26T15:37:36.448+0000",
"repositoryUrl": "http://tstest.alfresco.com/alfresco",
"alfrescoTenantId": ""
}
],
"start": 0
}
27. Activiti Administrator
This is an application that can be used to inspect and manage the data for an Activiti Enterprise engine (or cluster of engines). It also is used for cluster configuration and monitoring. It is distributed as a separate web application (WAR file).
Typically, there is one single Activiti Administrator application for multiple environments (e.g. development, testing, production, and so on), which is accessed by a handful of users (system administrators). As such, it is generally not necessary to have multiple instances of this application running (although it is certainly technically possible).
The Activiti Enterprise engine is cluster-enabled in the sense that, together with the Activiti Administrator, a user can configure and monitor a cluster (or multiple different clusters) through a graphical user interface. The clustered engines will use the same configuration and will report metrics and status back to the Activiti Administrator where they are displayed.
27.1. Installation
The Activiti Administrator is distributed as a regular WAR (Web Application ARchive) file that can be dropped in any Java web container.
Simply drop the activiti-admin.war file into the web container and start the web container. To make the application use your database, you must
-
copy the correct JDBC database driver to the classpath of the web application
-
create a property file called activiti-admin.properties that must be on the classpath of the web application. The properties must point to the correct environment settings. Note that there is such a file in the WEB-INF/classes/META-INF/activiti-admin that is used when no other properties file is found on the classpath.
27.2. Database configuration
The database is configured exactly the same as the SkyVault Activiti BPM Suite. See the database configuration section Activiti BPM Suite.
For example (using MySQL):
1
2
3
4
5
6 datasource.driver=com.mysql.jdbc.Driver
datasource.url=jdbc:mysql://127.0.0.1:3306/activitiadmin?characterEncoding=UTF-8
datasource.username=SkyVault
datasource.password=SkyVault
hibernate.dialect=org.hibernate.dialect.MySQLDialect
27.3. Cluster Configuration and Monitoring
The Activiti Administrator can show the process data and manage the configuration of multiple clusters. In this context a cluster is a number of Activiti Enterprise engines that logically belong together. Note that this does not relate to the way that these engines are architecturally set up: embedded, exposed through REST, with or without a load balancer in front, and so on.
Also note that the Activiti Administrator is capable of inspecting the information of each enterprise Activiti Process Engine (if configured correctly). Thus, it is not solely bound to using the process engine within the SkyVault Activiti BPM Suite, but to all enterprise Activiti process engines.
27.3.1. Architecture
Multiple clusters can be configured and managed through the Activiti Administrator. This is displayed in the drop-down in the top-right corner:
Each of the engines in a cluster should point to the same database schema. To access the data of a cluster, the Administrator application uses one Activiti REST application per cluster (to avoid direct access to the database from the Activiti Administrator or potentially to manage different engine versions).
The REST API endpoints can be included in your application using the Maven artifact com.activiti.activiti-rest. It is configured in a similar way as the Activiti Administrator.
No special setup is needed when using the SkyVault Activiti BPM Suite, as it contains the necessary REST API endpoints out of the box.
As shown in the diagram below, any cluster can consist of multiple Activiti engine nodes (pointing to the same database schema), the data that is managed in the Activiti Administrator is fetched through an Activiti REST application only.
In the same dropdown as shown above, a new cluster can be created. Note that a user will be created when doing so. This user is configured with the role of 'cluster manager' and is used to send information to the HTTP REST API of the Admin application, but it cannot log in into the Admin application as a regular user for safety reasons.
The REST endpoint for each cluster can be configured through the Activiti Administrator. Simply change the settings for the endpoint on the Configuration > Engine page while the cluster of choice is selected in the drop-down in the top-right corner. The current endpoint configuration is also shown on this page:
27.3.2. Configuration Settings
Information between an Activiti Process Engine and the Admin app is done through HTTP REST calls. To send or get information from the Admin Application, an Activiti Process Engine needs to be configured with a correct url and credentials.
For the engine, this can be done programmatically:
1
2
3
4
5
6 processEngineConfig.enableClusterConfig();
processEngineConfig.setEnterpriseAdminAppUrl("http://localhost:8081/activiti-admin");
processEngineConfig.setEnterpriseClusterName("development");
processEngineConfig.setEnterpriseClusterUserName("dev");
processEngineConfig.setEnterpriseClusterPassword("dev");
processEngineConfig.setEnterpriseMetricSendingInterval(30);
This configures the base HTTP API url, the name of the cluster that the engine is part of, the credentials of the user allowed to send data to the API and the time interval between sending data to the Admin application (in seconds).
The SkyVault Activiti BPM Suite includes the Activiti Process Engine. To enable engine clustering you can set the properties (similar to the programmatical approach) directly in the configuration file:
1
2
3
4
5
6 cluster.enable=true
cluster.config.adminapp.url=http://localhost:8081/activiti-admin
cluster.config.name=development
cluster.config.username=dev
cluster.config.password=dev
cluster.config.metricsendinginterval=30
The SkyVault Activiti BPM Suite also sends extra metrics to the Admin application. To configure the rate of sending, a cron expression can be set (by default the same as the rate of sending for the Process Engine):
1 cluster.config.app.metricsendingcronexpression=0/30 * * * * ?
Alternatively, it is possible to generate a jar file with these settings through the Configuration > Generate cluster jar button. This jar file can be placed on the classpath (or used as a Maven dependency in case of using a local Maven reposioty) of an engine or BPM Suite application and it will have precedence over the properties files.
Once the application is running, metrics for that node in the cluster are shown in the Admin application:
On the Admin application side of things, there are two settings that can be changed:
1
2 cluster.monitoring.max.inactive.time=600000
cluster.monitoring.inactive.check.cronexpression=0 0/5 * * * ?
-
cluster.monitoring.max.inactive.time : This a period of time, expressed in milliseconds, that indicates when a node is deemed to be inactive and is removed from the list of nodes of a cluster (nor will it appear in the 'monitoring' section of the application). When a node is properly shut down, it will send out an event indicating it is shut down. From that point on, the data will be kept in memory for the amount of time indicated here. When a node is not properly shut down (eg hardware failure), this is the period of time before removal, since the time the last event is received. Make sure the value here is higher than the sending interval of the nodes, to avoid that nodes incorrectly removed. By default 10 minutes.
-
cluster.monitoring.inactive.check.cronexpression ; A cron expression that configures when the check for inactive nodes is made. When executed, this will mark any node that hasn’t been active for 'cluster.monitoring.max.inactive.time' seconds, as an inactive node. Default: every 5 minutes.
27.4. Master Configuration
For each cluster, a master configuration can be defined. When the instance boots up, it will request the master configuration data from the Activiti Admin application. For this to work, the cluster.x properties (or equivalent programmatic setters) listed above need to be set correctly.
There is one additional property that can be set: cluster.master.cfg.required=. This is a boolean value, which if set to true will stop the instance from booting up when the Admin app could not be reached or no master configuration is defined. In case of false, the instance will boot up using the local properties file instead of the master configuration.
The master configuration works for both clusters of embedded Activiti engines or SkyVault Activiti BPM Suite instances. The two can not be mixed within the same cluster though.
Note: when changing the master configuration, the cluster instances would need a reboot. The Admin applicatio will show a warning for that node too in the 'monitoring' tab, saying the master configuration currently being used is incorrect.
27.5. HTTP Communication overview
Communication with the Admin Application is done using HTTP REST calls. The calls use HTTP Basic Authentication for security, but do use different users, depending on the user case.
The SkyVault Activiti BPM Suite and Activiti Admin Application do not share user stores. The reason for that is that
-
there is only a handful of users typically involved with the Admin Application.
-
the Admin Application can be used independently from the Suite Application.
The following pictures gives a high-level overview:
-
The Activiti Engine pushes and pulls data to and from the Admin Application REST API. These calls use basic authentication with a user defined in the Activiti Admin Application user store (relational database). Such a user is automatically created when a new cluster configuration is created (see above), but its credentials need to be configured on the engine/Suite app side (see the cluster.xx properties.
-
The Activiti Admin Application allows to browse and manage data in an Activiti Enterprise Engine. It calls the REST API to do so, using a user defined in the user store of the Suite Application (or any other authentication mechanism for the embedded engine use case).
For the SkyVault Activiti BPM Suite: this user needs to have a Tenant Admin or Tenant Manager role, as the Admin Application gives access to all data of the engine.
Let’s now focus on what this means for an end user:
An end user logs in through the UI, both on the Suite as the Admin Application. Again, the user store is not shared between the two.
It’s important to understand that the HTTP REST calls done against the Suite REST API, are done using the credentials of the Suite application using a user defined in the user store of the Suite Application. This user can be configured through the Admin Application UI.
In case of using LDAP, a equivalent reasonining is made:
The user that logs into the Admin Application is defined in the relational database of the Admin Application. However, the HTTP REST call will now use a user that is defined in LDAP.
27.6. Configuring the Activiti Rest app for usage with the Activiti Admin app
When using the Activiti engine embedded in a custom application (or multiple embedded engines), it is still needed to set up a REST endpoint that the Activiti Admin app can use to communicate with to see and manage data in the engines cluster.
The SkyVault Activiti BPM Suite already contains this REST api, so it is not necessary to add this additional REST app in that case.
Out of the box, the Activiti Rest application is configured to have a default admin user for authentication and uses an in memory H2 database. The latter of course needs to be changed to point to the same database as the engines are using.
The easiest way to do this, is to change the properties in the /WEB-INF/classes/META-INF/db.properties file with the correct datasource parameters. Make sure the driver jar is on the classpath.
To change the user that is created by default, change the settings in /WEB-INF/classes/META-INF/engine.properties. In the same file, basic engine settings can be configured:
-
engine.schema.update: whether the database schema must be upgraded on boot (if this is needed). Default true.
-
engine.asyncexecutor.enabled: whether the async job executor is enabled. Default false, as this is better done on the engine nodes itself, as otherwise you would have to make sure the classpath has all the delegates used in the various processes.
-
engine.asyncexecutor.activate: whether the async job executor is enabled. Default false, as this is better done on the engine nodes itself, as otherwise you would have to make sure the classpath has all the delegates used in the various processes.
-
engine.history.level: The history level of the process engine. Make sure this matches the history level in the other engines in the cluster, as otherwise this might lead to inconsistent data. Default 'full'.
In case these two property files are insufficient in configuring the process engine, it is possible to override the process engine configuration completely. This is done in a Spring xml file found at /WEB-INF/classes/META-INF/activiti-custom-context.xml. Uncomment the bean definitions and configure the engine without restrictions, similar to a normal Activiti Process Engine configuration.
The out of the box datasource uses C3P0 as connection pooling framework. In the same file, this datasource (and transaction manager) can also be configured.
The application uses Spring Security for authentication. By default, it will use the Activiti identityService to store and validate the user. In case this needs to be changed, add a bean with id 'authenticationProvider' to /WEB-INF/classes/META-INF/activiti-custom-context.xml. The class should implement the org.springframework.security.authentication.AuthenticationProvider interface (see Spring docs, multiple implementations available).
Note: the Rest app is not compatible with using a master configuration. It needs to be configured through the properties or the spring context xml/