Skip navigation
All Places > SkyVault BPM > Blog
1 2 Previous Next

SkyVault BPM

20 posts

This is a continuation of my previous blog post about data models Business Data Integration made easy with Data Models. In this example I'll be showing the integration of SkyVault Process Services powered by Activiti (APS) with Amazon DynamoDB using Data Models. Steps required to set up this example are:

 

  1. Create Amazon DynamoDB tables
  2. Model the Data Model entities in APS web modeler
  3. Model process components using Data Models
  4. DynamoDB Data Model implementation
  5. App publication and Data Model in action

 

Let’s look at each of these steps in detail. Please note that I’ll be using the acronym APS throughout this post to refer to SkyVault Process Services powered by Activiti. The source code required to follow the next steps can be found at GitHub: aps-dynamodb-data-model 

Create Amazon DynamoDB tables

As a first step to run this sample code, the tables should be created in Amazon DynamoDB service.

  1. Sign in to AWS Console https://console.aws.amazon.com/
  2. Select "DynamoDB" from AWS Service List
  3. Create Table "Policy"-> (screenshot below)
    1. Table name : Policy
    2. Primary key : policyId"
  4. Repeat the same steps to create another table called "Claim"
    1. Table name : Claim
    2. Primary key : claimId

Now you have the Amazon DynamoDB business data tables ready for process integration.

Model the Data Model entities in APS web modeler

Next step is to model the business entities in APS Web Modeler. I have already built the data models and they are available in the project. All you have to do is to import the "InsuranceDemoApp.zip" app into your APS instance. Please note that this app is built using APS version 1.6.1 which will not get imported in older versions. If you are using APS version 1.5.X or older, please import the app from my project I used in my previous blog post.

 

Once the app is successfully imported, you should be able to see the data models. A screenshot given below.

Model processes components using Data Models

Now that we have the data models available, we can now start using them in processes and associated components such as process conditions, forms, decision tables etc. If you inspect the two process models which got imported in the previous step, you will find various usages of the data model entities. Some of those are shown below:

 

 

Using Data Model in a process model


Using Data Models in sequence flows

 


Using Data Model in Forms

 


Using Data Models in Decision Tables (DMN)

 

 

Let’s now go to the next step which is the implementation of custom data model which will do the communication between process components and Amazon DynamoDB

 

DynamoDB Data Model implementation

In this step we will be creating an extension project which will eventually do the APS<-->Amazon DynamoDB interactions. You can check out the source code of this implementation at aps-dynamodb-data-model . For step by step instructions on implementing custom data models, please refer Activiti Enterprise Developer Series - Custom Data Models. Since you need a valid licence to access the SkyVault Enterprise repository to build this project, a pre-built library is available in the project for trial users - aps-dynamodb-data-model-1.0.0-SNAPSHOT.jar. Given below are the steps required to deploy the jar file.

  1. Create a file named "aws-credentials.properties" with the following entries and make it available in the APS classpath

aws.accessKey=<your aws access key>
            aws.secretKey=<your aws secret key>
            aws.regionName=<aws region eg:us-east-1>

  1. Deploy aps-dynamodb-data-model-1.0.0-SNAPSHOT.jar file to activiti-app/WEB-INF/lib

App publication and Data Model in action

This is the last step in the process where you can see the data model in action. In order to execute the process, you will need to deploy (publish) the imported app first. You can do it by going to APS App UI -> App Designer -> Apps → InsuranceDemoApp → Publish

Once the process and process components are deployed, you can either execute the process by yourselves and see it in action OR refer to video link in Business Data Integration made easy with Data Models demonstrating data model.

 

Once you run the processes, log back in to AWS Console https://console.aws.amazon.com/ and check the data in respective tables as shown below

 

 

That’s all for now. Again, stay tuned for more data model samples….

“Stencils” provide a very powerful set of capabilities to SkyVault Process Services powered by Activiti (APS) and can be used within the BPMN 2.0 Editor, the Step Editor and the Forms Editor.  Within the context of Forms, Stencils provide the facilities to develop custom form field types. The idea of this blog is to point you to some working form stencil samples.

In the recent weeks I had to build a few of these custom form stencils and thought it would be valuable to share it with the community. If you are someone who are just starting on stencils, I recommend you first read a 101 (introductory) blog on form stencils, which is available at Form Stencils 101.

In this blog I’ll take you through the following 5 form stencils:

  1. Custom Simple Text Input
  2. Grouping Fields using stencil
  3. Custom Grid/Table
  4. Signature Pad
  5. Rich Text Editor

Now let me try and explain these examples a bit more....

Simple Text Input Stencil

Source: github: simple-text-input-stencil

I built this component to demonstrate the implementation of a very simple custom input field using stencil. You might be wondering, why would I need to do this? Most often you may not need such a field in real life, however the idea of this stencil is to demonstrate the basic building blocks, or “Hello World” for a stencil implementation.

Grouping Fields using Stencil

Source: github: group-of-fields-stencil

One of the main use cases around this example is to build reusable domain specific field controls. Eg: Build a single form field called “Address” which will consist of fields such as Address Line, Town, State, Country, Zip Code etc. Building reusable form components very specific to your business data objects makes form modelling really easy. Given below is a screenshot of design time view and runtime view of my example.

 

Design Time View

At design time, the designer will select the custom stencil and configure it with some process data as shown below

Configuration

Run Time View

At runtime, the stencil will be displayed based on your configuration using your custom html, custom css etc

Custom Grid/Table

Source: github: angular-ui-grid-stencil

I built this component recently for a customer who wanted to display a list of records from a REST API in a tabular format. I have to say that it took only 15 minutes for me to do the research and implement this feature using the Angular UI Grid component. Since APS OOTB UI use this module for the dynamic table component, the angular modules I needed to implement this feature was already part of the product. That made my life easy! So, if you have complex table requirements that cannot be met by the OOTB table component, I recommend you to look at all the features available in this library at Angular UI Grid Tutorial and I’m pretty sure that a stencil component built using the features available in this library can meet most of your needs

 

Important - Since AngularJS doesn't allow module injection after application is bootstrapped, one cannot declare an external module as part of the stencil controller code.

Currently the only option to include an external AngularJS module into the "activitiApp" is by modifying "activiti-app/workflow/scripts/<minified script>.js" the following way.

eg: to add ngMap module from ng-map, one should this activitiApp=angular.module("activitiApp",[.,.,.,.,.,.,"ngMap"]) in the main module js file.

 

Signature Pad

Source: github: signature-pad-stencil

A signature stencil implementation based on Signature Pad

Thanks to Angular Signature for providing the AngularJS directive which I used in my Stencil implementation. My example app, demonstrates the following:

  1. Capture a signature
  2. Display of a signature captured in a previous task
  3. Display the signature in a document that is generated in the process.

Rich Text Editor

Source: github: tinymce-rich-text-editor

A rich text editor stencil implementation based on TinyMCE

Thanks to TinyMCE AngularJS Integration for providing the AngularJS directive which I used in my Stencil implementation.

 

General instructions to run the above examples

The zip archive files available in the above mentioned github source folders are “App” exports. To to use them, you will need to import them via App Designer (Kickstart App) -> Apps -> Import App. Once the “App” is successfully imported, the stencils along with an example process and form will also get imported which will make it easy for you to see these examples in action!

Finally..

Hopefully this is a good set of examples to show the power of stencil component in SkyVault Process Services. If you happen to build any cool form components using stencils, feel free to share it here in the form of a blog or video...

There are more examples available on the product documentation page - APS Docs: Custom Form Fields & Activiti Enterprise Developer Series - Custom Form Fields 

 

Note: I’m in the process of building SkyVault ADF (Application Development Framework) equivalent of these stencils and as soon as they are ready, I’ll make it available and update this blog with the reference. A 101 blog on implementing form stencil components in ADF can be found here - Custom stencils with SkyVault ADF

salaboy

Activiti 6 is on the Way

Posted by salaboy Employee Apr 26, 2017

Activiti 6.0 Final release is around the corner, and I’m super excited of being part of this release because it is the first step towards a bright future. I’m looking forward to working with the community to shape up the next releases of the project to make sure that we push BPM to the next level.

 

On the Activiti 6.0 release you will find:

  • A new refactored Process Engine optimized and battle tested by several community users and organizations. The introduction of native BPMN support instead of the Process Virtual Machines offers better performance. This builds on top of Activiti’s stateless architecture to bring even higher levels of performance and scalability.
  • The only Open Source BPM framework with the potential and plan for further bringing Content and Process together. Content enriches process management making it true context-aware digital flow rather than just state transitions and service orchestrations. Knowledge and context become the driver for semi-autonomous process.
  • Improved & flexible persistence mechanism which give us the right tools to move highly distributed environments. We are actively working on solutions that when combined with stateless architecture of Activiti’s process engine create new-infinite linear scalability. With Activiti’s small footprint this is ideal for cloud-era on-demand architectures. We are also supporting this direction with our first take in modularizing Activiti’s process services
  • Support for ad-hoc BPMN activities that combined with our Content roots broaden the scope and depth of unstructured and dynamic processes
  • Introduction of modern App Design modeller web app that offers in addition to BPMN modelling, forms, decision tables, granular capability-driven identity management, as well as the ability to bundle models into deployable model-driven process applications

 

I’ve joined SkyVault almost 4 months ago after being for more than 10 years contributing with several Open Source projects on the BPM and Model Driven Frameworks landscape. (You can find my articles, tutorials, books and my thoughts about BPM, Case Management, Rule Engines and AI in my personal blog: http://salaboy.com). I truly believe that we can simplify users experiences by providing the right tools for the job. Also, Developers and DevOps experiences can be simplified by making Activiti project even more ready for the Cloud and continuously updating to the latest standard technologies. I’m looking forward to help the Activiti community to evolve beyond traditional BPM Process Engines to a full-fledged Cloud Ready Business Automation Platform.

 

When I joined SkyVault, I realized the importance of completing the Activiti v6 work already in progress. You’ll see more innovation beyond the original v6 scope in the near future. In contrast with the how the Open Source project was previously organized, I’m committed to make sure that the next releases are built collaboratively between partners, community members and companies that are already using it.

 

Going forward, you will be see me around a lot in the community (http://github.com/salaboy, http://salaboy.com and http://twitter.com/salaboy) and conferences spreading the word. Please get in touch if you want to get involved.

dougjohnson

The Future of Activiti

Posted by dougjohnson Employee Apr 24, 2017

My name is Doug Johnson. I’m responsible for the BPM strategy at SkyVault working with the Activiti project and separately SkyVault’s commercial offerings. As a 20-year industry veteran with 17-years of BPM-specific experience, I’ve seen a lot of new business and solution requirements challenge BPM technologies. Ever increasingly, requirements are demanding even faster and simpler development cycles, better interactions and integration with systems and services, and compelling user experiences and engagement… to name a few trends.

 

The future of Activiti is strong. While statistics are hard to precisely determine, due to Activiti’s flexible licensing terms and easy-of-embeddability, Activiti is absolutely a top Open Source BPM project worldwide—it’s likely the most popular. It’s used to help developers create process-related solutions every day. Some of the largest companies in the world are powered by Activiti.

 

SkyVault, as the main project sponsor, is resolutely backing Activiti with experienced committers and community support. We have added to the Activiti team with outstanding engineers with rich process management and process intelligence backgrounds—including contributors from Bonita BPM, jBPM, Drools, and Pega—as well as those with data science and machine learning experiences including a PhD in advanced mathematics.

 

You’ll be hearing from these new Activiti committers soon and more regularly. At the helm, Mauricio Salatino joins Activiti from a history of Open Source success. He is one of many persons bringing a fresh perspective as Activiti continues to deliver the most relevant process technologies for today and tomorrow’s needs.

 

For those attending BeeCon 2017, I look forward to meeting you this week. I and others will of course be sharing some of the latest in Activiti, including some exciting announcements. Just as important will be the conversations and discussions with you. For those unable to attend BeeCon 2017, watch the forums for pending announcements on further opportunities to interact with the Activiti team in May 2017.

Business Process Management (BPM) best practices often suggests that the BPM solution not be the system of record. In particular, the business data required for the Digital Business Solution should exist in other data stores outside of the persistence store being used by the BPM engine itself. Business data that is used and or created during the execution of a business process should exist and be maintained in one or more external data stores (e.g. RDBMS, NoSQL, etc...). Therefore to simplify and accelerate the development of enterprise scale Digital Business Solutions, SkyVault Process Services (Alfresco’s Enterprise Edition of the Activiti Community Edition (Open Source)) provides an important and valuable component called "Data Models" which is the focus of this blog.

Please note that I’ll be using the acronym APS throughout this post to refer to SkyVault Process Services.

Business Data Integration in BPM solutions

If you haven’t read this blog post (a bit old, but still very relevant) Storing data in automated business processes :: AirQuill  & More on Orchestration Data :: AirQuill  already, you must read it first (especially the first blog) before you go into my next section. This is a great post explaining why storing data outside of process engine tables is so important!

Integration options available in APS

Good news is, APS provides a variety of options for customers to do the business data integration. Listing down below all the data integration options that are available when implementing business processes using Activiti engine:

    The complete source code associated with the above video presentation along with a detailed readme file is available at https://github.com/cijujoseph/activiti-examples/tree/master/activiti-custom-data-model-sample

     

    I'll be creating more data model examples in the coming months! Stay tuned...

Table of Contents                   

Introduction

The Activiti Enterprise BPM solution comes with a number of preconfigured Activities that can be used to communicate with the SkyVault ECM system. The following picture shows what features are available:

These are basically specialized Service Task implementations that talk to the SkyVault content management system. In this article we will have a look at how to use them to:

 

     

    Here we got three different types of properties in different namespaces. First we got the standard CMIS properties in the cmis: namespace. For more info about available CMIS properties see this page and the 2.1.4.3.3 Property Definitions section for cmis:document. After that we are fetching some properties from the out-of-the box content model with the cm: namespace. And finally we fetch some properties from our custom model just to demonstrate how that is done.

     

    To display the fetched properties let’s use a User Task:

     

     

    Configure a form for it as follows:

     

     

    So here we are using a number of Display value form fields that will just display value of corresponding process variable. We have grouped the properties with Header form fields.

     

    Now, save the process model and republish it.

     

    Start a process instance and fill in the start form as follows:

     

     

    The new User Task should display the fetched properties as follows:

     

     

    We can see that all properties have been fetched successfully, except the date properties. This has to do with the problem described earlier on when Activiti is representing dates incorrectly internally. It does not work to fetch the date properties as strings. To get around this we would have to implement a custom Java based Service Task that uses for example OpenCMIS to fetch the date properties.

     

    Worth noting here also is that the title and description are usually populated via metadata extraction in SkyVault, and in this case the PDF file contained a description property that the cm:title was populated with.

     

    Related bug report.

    Updating properties for SkyVault Content

    Ok, so we now know how to upload files from Activiti to SkyVault and how to retrieve properties for files in SkyVault from Activiti. There might also be scenarios where we select files in SkyVault repository for processing in an Activiti process instance (in contrast to selecting them from the local file system as done so far in this article), and at the end of the process instance we want to set some properties for these files.

     

    This can be done with the “Update SkyVault properties” task. Let’s try it out by changing some of the properties we have for our files. Add first a User Task, which will be used to set new property values, and then add an Update SkyVault properties task. You should now have a process model looking something like this:

     

     

    Configure a form for the Set new Prop values User task as follows:

     

     

    The form will also show current values for reference.

     

    Now, for the “Update SkyVault properties” task click on the SkyVault properties property for it and fill it in as follows:

     

     

    Here we map the form fields we just created to the content model properties. This is straight forward, except for the date property, which has to be configured as a string for it to work as has been discussed before.

     

    We also specify what user that should be used to make the update, remember that this user has to be mapped to a SkyVault user that has write permissions to the repository. In this case we just use the process initiator, which is admin.

     

    Now, save the process model and republish the application.

     

    Then start a new process instance and fill in the first start form as follows:

     

     

    Complete the “Show retrieved props” task so you get to the Set new Prop values task:

     

     

    Fill in some new values to the left:

     

     

    Then click COMPLETE.

     

    Now, in the Share UI navigate to one of the files and the Details page for it and verify that the update was successful:

     

     

    Related bug report.

    Calling Repository Actions in SkyVault

    Ok, so we now know how to upload files to the SkyVault Repository and how to classify them at the same time, or at a later point. Next step might be to do some processing on the files we just uploaded. When we process files in SkyVault we usually execute something called a Document Library Action in the front-end (i.e. Share UI), which in turn invokes a so called Repository Action in the back-end.

     

    From Activiti we can invoke Repository Actions via a task called “Call SkyVault Action”. Let’s walk through how to call a number of the out-of-the-box repository actions.

    Apply an Aspect to a file

    A common requirement is to be able to apply a new aspect to an existing content file in SkyVault. This can be done by invoking the add-features Repository Action. In this case it is not enough to just use the “Update SkyVault properties” task as it will not add a new aspect, just set properties. What we need to do is to first use the “Call SkyVault Action” task to apply the aspect and then set the properties.

     

    For this example we will create a new process model as follows:

     

     

    You might be wondering why we can not provide the aspect properties at the same time as we apply the aspect? That is not possible with the remote REST API that is used by the “Call SkyVault Action”. If you think about how it works from the Share UI when using “Manage Aspects”, or applying an aspect via rules. Then it’s also not possible to provide the aspect properties at the same time, you usually set them via Edit Properties afterwards. We need to use the “Update SkyVault properties” task to set the aspect properties.

     

    For a solution on how to apply the aspect and its properties at the same time see next section on executing a script.

     

    For the Start Event we create a form where we can select a SkyVault file that we want to execute the action on:

     

     

    We also add some fields for the properties that are part of the cm:emailed aspect we want to apply to the file. Configure the Attach field so we can only select content files from SkyVault Repository and the Case Management site:

     

     

    Then, for the “Call SkyVault Action” task, select Repository and Content (i.e. the file form field we just configured in the start form):

     

    And then finally click on the Action field for the task and configure the Repository Action that we want to invoke on the file:

     

    The Action field drop down contains some pre-configured actions that we can choose from, select the Add Aspect action, which will resolve to the action id add-features. You can also type in the Repository Action ID (i.e. the Spring Bean ID for the action) manually if it is not in the list. See this file for a list of actions and their IDs (look for beans with parent="action-executer").

     

    When selecting the Add Aspect action a property called aspect-name is automatically added to the list of parameters that should be passed to the action invoker. It should be set to the aspect name that we want to apply to the file, in this case the out-of-the-box aspect cm:emailed.

     

    Then we specify what aspect related properties we want to set via the “Update SkyVault properties task”, and from what form fields to grab the values:

     

    Remember that Activiti represents dates incorrectly internally so when we set properties we need to define the date property as a string for it to work, see above.

     

    Now let’s try this process model. After starting a process instance we get presented with the Start Form where we should select a file from the Case Management site and fill in some random email info:

     

    Click START PROCESS to get the aspect applied. If you navigate to the file in SkyVault Share you should see the following under the Properties section:

     

     

    The cm:emailed aspect has a form defined so that’s why we see these properties nicely laid out. But we still don’t have any form defined for myc:caseDocument so the other properties will not be visible. But you can always go via the Node Browser to see all properties.

     

    Important, if the first “Call SkyVault Action” executes successfully but the second “Update SkyVault properties” task fails, then you would have a repository in an inconsistent state. To have both the apply aspect and set properties operations in the same transaction you would have to implement a custom service task and handle all the updates manually. Or use a script as in the next section.

    Calling a Script

    Another thing that is useful to be able to do from Activiti is to execute a JavaScript file in the Repository. This can be done by executing the script Repository Action. Create a new process model with a Start Event form that has an attachment field. Then add the “Call SkyVault Action” task.

     

    Configure the Content and Repository properties for the task as follows:

     

     

    So we now know what repository to talk to and what file to apply the script to. Next step is to configure the Action property for the task:

     

    Select Execute a script from the Action drop down. This will add the mandatory script-ref parameter. The parameter has to be set to the Node Reference for the JavaScript file in the repository. For example: workspace://SpacesStore/fe60c4b5-ea73-4ef8-9b5e-33d2772c5bf6

     

    To figure out what the script-ref should be add a JavaScript file called applyAspect.js to the /Company Home/Data Dictionary/Scripts folder in the SkyVault Repository:

     

     

    Set the Mimetype to JavaScript.

     

    It should have JavaScript code looking like this:

     

    var props = new Array();
    props['cm:originator'] = 'martin@mycompany.com';
    props['cm:addressee'] = 'somebody@mycompany.com';
    props['cm:subjectline'] = 'Testing Some stuff';
    props['cm:sentdate'] = new Date();
    document.addAspect("cm:emailed", props);

     

    Now, to find out the Node Reference for this script have a look at the Details Page for the file in SkyVault Share, it will have a link property with this information:

     

     

    Copy the nodeRef value and set it as the value for the script-ref action parameter.

    Now run this process model. We should see a similar effect on the file as in the Apply Aspect action execution.

     

    Being able to execute a script like this gives you almost unlimited possibilities in processing the Repository content. However, it is not possible to pass parameters to the script, so this action probably needs to work in conjunction with some other task.

    Set Content Type for a file

    Another very useful thing to be able to do from Activiti is to set a new type for a content file in SkyVault. This can be done by executing the specialise-type Repository Action. Create a new process model with a Start Event form that has an attachment field. Then add the “Call SkyVault Action” task.

     

    Configure the Content and Repository properties for the task as follows:

     

     

    So we now know what repository to talk to and what file to set the content type on. Next step is to configure the Action property for the task:

     

    Select Specialise type from the Action drop down. This will add the mandatory type-name parameter. The parameter has to be set to the content model type name. For example: myc:caseDocument. Note that for this action to have any effect the type we set must extend the type already set on the content file.

     

    Because the files in the Case Management site already have the myc:caseDocument type set, we will have to upload a new file to the Case Management site’s Document Library before we try this out.

    Moving a file

    One quite useful thing to be able to do from Activiti is to move a file in the SkyVault Repository after some processing have been done on it in the workflow, maybe something like a review and approval. This can be done by executing the move Repository Action. Create a new process model with a Start Event form that has an attachment field. Then add the “Call SkyVault Action” task.

     

    Configure the Content and Repository properties for the task as follows:

     

     

    So we now know what repository to talk to and what file to move. Next step is to configure the Action property for the task:

     

     

    Select Move from the Action drop down. This will add the mandatory destination-folder parameter. The parameter has to be set to the SkyVault Node Reference for the folder where you want to move the file. For example: workspace://SpacesStore/2a8052aa-e5ec-4546-85bf-49f55f6b7e81

     

    In this sample I have just created an Approved folder in the Case Management site’s Document Library:

     

     

    Now, to find out the Node Reference for this folder have a look at the Folder Details Page in SkyVault Share, it will have a link property with this information:

     

     

    Copy the nodeRef value and set it as the value for the destination-folder action parameter.

     

    Now run this process model. We should see that the selected file is moved into the Approved folder.

    Table of Contents                   

     

    Introduction

    Something that comes up quite often in Activiti Forums is how to add a new form field type and render it in the Activiti Application UI. Doing this is actually quite easy. We will look at a couple of examples. The first one is a simple Month field presented as a dropdown where the end-user can select a month. This is all static and involves no dynamic data. However, in many solutions we need to load a dropdown or a list with data from an external service. The second example shows one way of doing that via a REST call. And finally we will have a look at an example that also involves custom parameters specified by the form designer.

     

    If you are working with an earlier version of Activiti that has the Explorer UI, then see this blog for how to do this.

     

    This article covers the new Angular based UI.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    There is no need to set up an extension project when working with custom form fields, it is all done via the Activiti Application UI.

    Create a Custom Form Stencil

    All the custom form fields that we create will be part of a custom form stencil. You can think of a custom form stencil as a custom Form Editor configuration.

     

    Stencils are created via the Kickstart App:

     

     

    Clicking on this application shows the following page:

     

     

    Click on the Stencils menu item at the top and then on the Create a new Stencil now! Button:

     

     

    Here I have to select Form editor from the Editor type drop down. We should now have a custom stencil as follows:

     

    Implementing a Month Form Field

    Let’s start with a simple example where we want to add a form field type that allows the user to select a month. Visually, we want to use a dropdown list for that on the UI side of things.

     

    When finished we will have something that looks like this:

     

     

    And whatever month is selected will be available in a process variable when the form is completed.

     

    When designing a form the month field type will look like this:

     

    Creating the Form Field in the Custom Stencil

    Open up the “My Company Form Editor” stencil in the Stencil Editor:

     

     

    Here we have the default form editor palette of fields that we can use when creating forms. Now, to add a new field type, click on the +Add new item in the upper right corner, this displays the following screen:

     

     

    The properties in this screen have the following meaning:

     

    • Name: the name of this field in the Form Editor palette
    • Description: the description of this field in the Form Editor palette
    • Internal identifier: id that can be used to access the field control in JavaScript
    • Icon: the icon of this field in the Form Editor palette
    • Field width: how many columns should this field take up in the finished form shown to user
    • Form runtime template: the markup that makes up the UI representation of the field, as shown to the end-user
    • Form editor template: what you the form designer see when using this field in a form

     

    Fill in the properties like this (to the right we can see where the properties are used in the form designer or in the finished form as shown to the end user):

     

     

    In this case we have given the new field the name Month, and it will show up as that in the Form Editor palette just after the icon. The icon is a custom one that is based on the text field one but with some extra month list text in it.

     

    The Form runtime template has been filled in with HTML markup that will be used to display the field in the form as seen by the end-user. The UI is written in Angular version 1, so there is some Angular directives in the code, such as ng-controller. It will bind this UI code to a Angular controller called monthController, which we have to define later on.

     

    There is also the ng-model Angular directive that can be used to do a data binding so when you select something in the dropdown, the value is stored in the data.selectedMonth JavaScript variable. The data binding is bidirectional so we can initialize the dropdown (i.e. pre-select) in the controller if we want to.

     

    We also need to define how the new field should be displayed in the form editor when dragged onto the form canvas. This is done with the Form editor template, keep this display static and simple as it does not support parameters.

     

    Next step is to implement the Angular controller for the field, this is done by clicking on the Edit button in the field creator screen:

     

    This brings up the following dialog where we can implement the controller:

     

     

    The following code attaches this Controller to the HTML markup we just created (i.e. the View):

     

    angular.module('activitiApp').controller('monthController'

     

    The following function declaration starts the controller implementation:

     

    function ($rootScope, $scope) {

     

    Inside the controller we first need to set up the JavaScript object that is used for the data binding to the dropdown control:

     

    $scope.data = {
        selectedMonth: null,
    };

     

    This is also where you could initialize more variables that should be used in the dropdown control, such as initially selected option. For more information about scopes, see Angular scope guide.

     

    After this we hook into one of the extension points in the UI called formBeforeComplete, which will be called just before the end-user completes the user-task. There are a number of empty extension point functions that we can implement to get called back at certain points/events in the UI lifecycle. See these docs for a full list of extension points.

     

    These extension functions are all defined in the SkyVault.formExtensions object. Our implementation looks like this:

     

    SkyVault.formExtensions.formBeforeComplete =
        function(form, outcome, scope) {
            console.log('Before form complete');
            $scope.field.value = $scope.data.selectedMonth;
        };  

     

    What this code does is making sure that what was selected in the month drop down box, such as the value feb, for month February, is set in the field value as specified by the form designer. Basically, when the form designer uses our control, they will give the field an ID, such as this:

     

    The following code make sure that the selectamonth field will have the selected month value set:

     

    $scope.field.value = $scope.data.selectedMonth;

     

    This means also that there will be a process variable with the name selectamonth available with the value that the end-user selected.

    Trying out the Month Field

    Create a simple process model with a single User Task as follows:

    Then create a form for the user task by clicking on the Referenced form property for it and then click New form button:

     

    Call the new form Select Month and make sure to select the My Company Form Editor stencil that we have just customized with the Month field:

     

    Drag-and-drop the new Month field into the form canvas and add also a standard text field so we can see how they are presented together. Fill in the Month field properties as follows:

     

    Now, create an Application and add this process model to it so we can publish it and execute it. For more information about this see this blog section.

     

    Start a process instance and you should see a User Task form as follows:

     

    Select a month and fill in some text:

     

    Then click the COMPLETE button.

     

    If we were to install an execution listener on the sequence flow after the user task, and it printed process variables, then we would see something like this:

     

    04:29:37,387 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - --- Process variables:

    04:29:37,387 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [specifysometext = Activiti is cool!]

    04:29:37,387 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [initiator = 1]

    04:29:37,387 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [selectamonth = Aug]

    Debugging an Angular Controller

    Implementing Angular controllers can sometimes be a bit frustrating if you don’t know how to debug them. To debug JavaScript code we can for example use the FireBug plugin.

     

    What we want to do first is to kick off a new process instance so we see the user task as follows:

     

     

    Now, start FireBug in detached mode, or whatever other JS debugger you are using:

     

     

    If you are not in the Script tab switch to it. To set a breakpoint in the controller code select the  controllers file from the drop down in upper left corner:

     

     

    Set breakpoints by clicking in the area to the left of the line numbers. In this case I have set one breakpoint just at the entry of the controller and one inside the callback function, which is useful as I can then be sure the extension point is actually called.

     

    Select a month and fill in some text and then click COMPLETE, the debugger should stop at the breakpoint inside the extension point callback code:

     

     

    You can inspect variables to the right in the Watch area. This should give you an idea of how to debug your Angular Controllers.

    Implementing a Dynamic Form Field

    In this example we will see how we can populate a dropdown with data from a REST call.

    What the end result look like

    In this example we will use an external Web Service that can return a list of the US States.

     

    We call the web service like this:

     

    http://services.groupkt.com/state/get/usa/all

     

    {
     "RestResponse" : {
       "messages" : [ "More webservices are available at

    http://www.groupkt.com/post/f2129b88/services.htm",

    "Total [56] records found." ],
       "result" : [ {
         "country" : "USA",
         "name" : "Alabama",
         "abbr" : "AL",
         "area" : "135767SKM",
         "largest_city" : "Birmingham",
         "capital" : "Montgomery"
       }, {
         "country" : "USA",
         "name" : "Alaska",
         "abbr" : "AK",
         "area" : "1723337SKM",
         "largest_city" : "Anchorage",
         "capital" : "Juneau"
       }, {
         "country" : "USA",
         "name" : "Arizona",
         "abbr" : "AZ",
         "area" : "294207SKM",
         "capital" : "Phoenix"
       },...

     

    It responds with JSON and we can get to the list of states via the RestRespone.result property.

     

    The end-user will see the following dropdown in a form that uses this field:

    And whatever state is selected will be available in a process variable when the form is completed.

     

    When designing a form the state field type will look like this:

     

    Prerequisites

    As this involves making a call to another domain it will not work straight away because of the same origin policy applied by the web browsers. If we just went on with this implementation and called the other domain (i.e. not the local domain where we loaded the Activiti Application), then we would see the following type of error messages during debugging:

     

    Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://services.groupkt.com/state/get/usa/all. (Reason: missing token 'cache-control' in CORS header 'Access-Control-Allow-Headers' from CORS preflight channel).

     

    We can easily get around this by installing a Proxy in front of the Activiti Server and the External Web Service. We would access everything through the proxy so to the browser it looks like everything is coming from the same origin.

     

    Note. if the server supports JSONP, such as the SkyVault 2.0 server, you could get around installing an extra proxy. Angular $http supports JSONP calls.

    Installing NGINX proxy

    The NGINX server is a useful piece of software that makes it easy to set up a Reverse Proxy locally. It has a similar function as Apache HTTP Server. The installation is different for different operating systems, here is how I installed it on Ubuntu (see the NGINX site for more info on how to install on other platforms):

     

    $ sudo netstat -nlp |grep :80

    tcp6       0      0 :::80                   :::*                    LISTEN      2922/apache2

    $ /etc/init.d/apache2 stop

    [ ok ] Stopping apache2 (via systemctl): apache2.service.

     

    $ sudo apt-get install nginx

    Setting up nginx-core (1.9.3-1ubuntu1.2) ...

    Setting up nginx (1.9.3-1ubuntu1.2) ...

    Configure NGINX proxy

    So we want to configure the proxy with location of both the Activiti server and the external web service. Open up the default site configuration:

     

    martin@gravitonian:/etc/nginx/sites-available$ sudo gedit default

     

    And change it so it looks like this:

     

    server {

        listen                          8888;

        server_name                     dev-proxy;

     

        access_log  /var/log/nginx/dev-proxy.log;

        error_log  /var/log/nginx/dev-proxy.error.log;

     

        location /state {

              proxy_pass  http://services.groupkt.com;

        }

     

        location /activiti-app/ {

       proxy_pass  http://localhost:8080;

       proxy_pass_header  Set-Cookie;

       proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

     

       proxy_redirect          off;

       proxy_set_header        Host            $host;

       proxy_set_header        X-Real-IP       $remote_addr;

       proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;

       client_max_body_size    10m;

       client_body_buffer_size 128k;

       proxy_connect_timeout   90;

       proxy_send_timeout      90;

       proxy_read_timeout      90;

       proxy_buffers           32 4k;

        }

    }

     

    Then restart the proxy:

     

    $ sudo service nginx restart

     

    You should be able to access the proxy via browser now at http://localhost:8888/ and see something like this:

     

    Now, try and access the external web service at http://services.groupkt.com/state/get/usa/all via the proxy on http://localhost:8888/state/get/usa/all:

     

     

    And Activiti at http://localhost:8080/activiti-app via the proxy at http://localhost:8888/activiti-app:

     

     

    Ok, that’s it, we are all set now to start implementing our US state dropdown.

    Creating the Form Field in the Custom Stencil

    Open up the “My Company Form Editor” stencil in the Stencil Editor:

     

     

    To add a new field type, click on the +Add new item in the upper right corner. Fill in the form as follows (to the right we can see where the properties are used in the form designer or in the finished form as shown to the end user):

     

    In this case we have given the new field the name US State, and it will show up as that in the Form Editor palette just after the icon. Upload an icon that fits the field.

     

    The Form runtime template has been filled in with HTML markup that will be used to display the field in the form as seen by the end-user. It will bind this UI code to an Angular controller called usSateController, which we have to define later on.

     

    The ng-model Angular directive is used to do a data binding so when you select something in the dropdown, the value is stored in the data.selectedState JavaScript variable. The data binding is bidirectional so we also use it to fill the dropdown with options, via the ng-options Angular directive, from the data.states property, which we will set up in the controller implementation.

     

    We then define how the new field should be displayed in the form editor when dragged onto the form canvas. This is done with the Form editor template, keep this display static and simple as it does not support parameters.

     

    Next step is to implement the Angular controller for the field, this is done by clicking on the Edit button in the field creator screen:

     

     

    This brings up the following dialog where we can implement the controller:

     

    The full JavaScript code looks like this:

     

    angular
    .module('activitiApp')
    .controller('usSateController',
    ['$rootScope', '$scope', '$http',
      function ($rootScope, $scope, $http) {
           $scope.data = {
               selectedState: null,
               states: null
           };
        
           // Fetch all the states from an external REST service
           // that responds with JSON
           $http.get('http://localhost:8888/state/get/usa/all').
               success(function(data, status, headers, config) {
                   var tempResponseArray = data.RestResponse.result;
                   $scope.data.states = [];   
                   for (var i = 0; i < tempResponseArray.length; i++) {
                       var state = { name: tempResponseArray[i].name,
                                     code : tempResponseArray[i].abbr };
                       $scope.data.states.push(state);   
                   }   
               }).
               error(function(data, status, headers, config) {
                       alert('Error: '+ status);
                       tempResponseArray = [];
               });         

           // Setting the value before completing the task so it's properly stored
           SkyVault.formExtensions.formBeforeComplete =
               function(form, outcome, scope) {
                   $scope.field.value = $scope.data.selectedState;
               };    
      }]
    );

     

    The following code attaches this Controller to the HTML markup we just created (i.e. the View):

     

    angular.module('activitiApp').controller('usSateController' 

     

    The following function declaration starts the controller implementation:

     

    function ($rootScope, $scope, $http) {

     

    Note here that we also bring in the $http Angular module so we can execute our REST call to the Web Service.

     

    Inside the controller we first need to set up the JavaScript object that is used for the data binding to the dropdown control:

     

    $scope.data = {
        selectedState: null,
        states: null
    };

     

    For more information about scopes, see Angular scope guide.

     

    We then make the Web Service call via the $http service:

     

    $http.get('http://localhost:8888/state/get/usa/all').
        success(function(data, status, headers, config) {
            var tempResponseArray = data.RestResponse.result;
            $scope.data.states = [];   
            for (var i = 0; i < tempResponseArray.length; i++) {
                var state = { name: tempResponseArray[i].name,
                              code : tempResponseArray[i].abbr };
                $scope.data.states.push(state);   
            }   
        }).
        error(function(data, status, headers, config) {
            alert('Error: '+ status);
            tempResponseArray = [];
        });  

     

    Note here that we make the Web Service call via the proxy at localhost:8888. The get() call is asynchronous and will return promises, so we need to make sure we do all processing inside the success() method. Otherwise we will try and do the processing before the REST call returns. The $http service will automatically handle JSON responses and we can grab the JSON Array straight from the data.RestResponse.result property. We then get the State Name and Code from the name and abbr properties respectively.

     

    After this we hook into one of the extension points in the UI called formBeforeComplete, which will be called just before the end-user completes the user-task. Our implementation looks like this:

     

    SkyVault.formExtensions.formBeforeComplete =
         function(form, outcome, scope) {
             $scope.field.value = $scope.data.selectedState;
         };           

     

    What this code does is making sure that what was selected in the state drop down box, such as the value FL, for state Florida, is set in the field value as specified by the form designer. Basically, when the form designer uses our control, they will give the field an ID, such as this:

     

     

    The following code make sure that the chooseusstate field will have the selected state value set:

     

    $scope.field.value = $scope.data.selectedState;

     

    This means also that there will be a process variable with the name chooseusstate available with the value that the end-user selected.

    Trying out the US State Field

    Continuing with the process and user-task form we created earlier on for the Month field. Important, be sure to access Activiti via the proxy at http://localhost:8888/activiti-app. Open up the form and add a US State field to it:

     

    Drag-and-drop the new US State field into the form canvas and fill in the field properties as follows:

     

     

    Now, re-publish the application to get the changes to take effect.

     

    Start a new process instance and you should see a User Task form as follows:

     

     

    Select a month, fill in some text, and choose a US State:

     

     

    Then click the COMPLETE button.

     

    If we were to install an execution listener on the sequence flow after the user task, and it printed process variables, then we would see something like this:

     

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - --- Process variables:

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1Proc = Hello World!]

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [chooseusstate = HI]

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [specifysometext = Activiti is cool!]

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1ProcLocal = Hello World Local!]

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [initiator = 1]

    10:44:44,216 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [selectamonth = null]

    Implementing a Hyperlinked Image Form Field

    So far all our example custom fields have not needed any form designer input. Meaning they have needed no configuration at the time they were included in the user-task form. However, there might be certain custom form fields that need extra parameters for configuring the field when it is included in the form.

     

    The next example will show how to use custom parameters for a form field. We will implement a hyperlink image field, which will need the form designer to supply both the image link and the hyperlink at the time when the form is designed.

    Creating the Form Field in the Custom Stencil

    Open up the “My Company Form Editor” stencil in the Stencil Editor:

     

     

    To add a new field type, click on the +Add new item in the upper right corner. Fill in the form as follows (to the right we can see where the properties are used in the form designer or in the finished form as shown to the end user):

     

    In this case we have given the new field the name Image Hyperlink, and it will show up as that in the Form Editor palette just after the icon. Upload an icon that fits the field.

     

    The Form runtime template has been filled in with HTML markup that will be used to display the field in the form as seen by the end-user. In the above example the configuration parameters (i.e. hyperlinkURL and imageURL) have been set to point to the Activiti Org site.

     

    We then define how the new field should be displayed in the form editor when dragged onto the form canvas. This is done with the Form editor template, keep this display static and simple as it does not support parameters.

     

    Next step is to define the two configuration parameters. Click on the Edit link at the bottom of the field creator screen:

     

     

    This brings up the following dialog where we can define custom tabs and properties:

     

     

    Start by adding a new Tab called Image and position it between the two existing tabs, then add the first property with id hyperlinkURL:

     

     

    Click Save property. Then add the other property with id imageURL:

     

     

    Click Save property. And then save the stencil.

     

    When you specify the IDs for the properties it is important that they match what has been coded in the Form runtime template.

    Continuing with the process and user-task form we created earlier on for the other custom fields. Important, be sure to access Activiti via the proxy at http://localhost:8888/activiti-app as we still have the dynamic form field. Open up the form and add an Hyperlink Image field to it:

     

     

    Then hover over the field and click the pen icon to the right to edit properties:

     

    Specify a hyperlink URL and an image URL.

     

    Now, re-publish the application to get the changes to take effect.

     

    Start a new process instance and you should see a User Task form as follows:

     

     

    If you click on the Activiti Logo it should take you to the http://www.activiti.org site.

    Table of Contents                   

     

    Introduction

    The Activiti Enterprise application comes with an extensive REST API. However, sometimes it might be useful to add your own REST API, to for example support a domain specific workflow implementation. However, before starting any major REST API development, make sure that you are familiar with the REST API that is available out of the box, so you don’t implement stuff that is already supported. It might also at this point be a good idea to read up a bit on the Spring Web MVC framework, which is what the Activiti REST API is based on.

     

    Here is a quick summary to get you going. Spring’s annotation based MVC framework simplifies the process of creating RESTful web services. The key difference between a traditional Spring MVC controller and the RESTful web service controller is the way the HTTP response body is created. While the traditional MVC controller relies on the View technology, the RESTful web service controller simply returns the object and the object data is written directly to the HTTP response as JSON or XML.

     

    In the traditional workflow the ModelAndView object is forwarded from the controller to the client. Spring lets you return data directly from the @Controller, without looking for a view, using the @ResponseBody annotation on a method. When you use the @ResponseBody annotation on a method, Spring converts the return value and writes it to the HTTP response automatically. Each method in the Controller class must be annotated with @ResponseBody.

     

    Beginning with Spring version 4.0, this process is simplified even further with the introduction of the @RestController annotation. It is a specialized version of the controller which is a convenience annotation that does nothing more than adding the @Controller and @ResponseBody annotations. By annotating the controller class with @RestController annotation, you no longer need to add @ResponseBody to all the methods that should map to requests. The @ResponseBody annotation is active by default. You can read more about it here.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your REST API implementation make sure to set up a proper Activiti Extension project.

    Implementing a Custom REST API

    This section walks you through the process of creating a "Hello World" REST API with Spring.

     

    We will build a REST endpoint that accepts the following HTTP GET requests:

     

    http://localhost:8080/activiti-app/app/hello:

     

    http://localhost:8080/activiti-app/app/hello?name=Martin:  

     

     

    http://localhost:8080/activiti-app/app/hello/martin:

     

     

    As you can see, we can make a GET request to the endpoint in three different ways, without any parameters with /hello, with a query parameter like /hello?name={name}, and with a path parameter like /hello/martin. The response to all these requests will be XML. We will see later on how to change it to also support JSON.

    Implementing the REST Controller

    In Spring’s approach to building web sites, HTTP requests are handled by a controller. You can easily identify these request classes by the @RestController annotation. In the following example, the HelloWorldController handles GET requests for /hello by returning XML.

     

    package com.activiti.extension.rest;

    import org.springframework.web.bind.annotation.*;

    @RestController
    @RequestMapping("/hello")
    public class HelloWorldRestController {

      @RequestMapping(value = "/", method= RequestMethod.GET)
      public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                    defaultValue="World") String name) {
          GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
          return msg;
      }

      @RequestMapping(value = "/{name}", method= RequestMethod.GET)
      public GreetingMessage sayHelloAgain(@PathVariable String name) {
          GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
          return msg;
      }
    }

     

    Note here in which Java package this class is located in: com.activiti.extension.rest. This will register this endpoint as available under /activiti-app/app URL path. It is then meant to be used in the application UI. And it will use cookie based authentication. So if we are logged into the Activiti Application when trying this REST endpoint, then there will be no need to authenticate.

     

    The use of the @RestController annotation marks this class as a controller where every method returns a domain object/POJO instead of a View. All URLs will start with /hello for this controller. We are responding with a domain object converted into format understood by the consumers. In our case, due to the Jackson library being included in the class path, the GreetingMessage object will be converted into XML format (or into JSON as we will see later on).

     

    The @RequestMapping annotation specifies the exact request that will map to the class and method, for example /app/hello/martin will map to the class and the sayHelloAgain method. The request method (i.e. GET, POST etc) has no default value so we need to set it to something, HTTP GET in this case.

     

    For the response to be converted into XML we also provide the following data transfer object class:

     

    package com.activiti.extension.rest;

    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import java.io.Serializable;

    @XmlRootElement(name = "greeting")
    public class GreetingMessage implements Serializable {
      String name;
      String text;

      public GreetingMessage() {}

      public GreetingMessage(String name, String text) {
          this.name = name;
          this.text = text;
      }

      @XmlElement(name = "name")
      public String getName() {
          return name;
      }

      @XmlElement(name = "text")
      public String getText() {
          return text;
      }
    }

     

    This class uses the JAXB annotations to tell Spring MVC what XML elements to use in the responses.

    Making the REST endpoint part of Public API

    So far we created a REST endpoint that is available in the Activiti Application context with cookie based authentication. This is not ideal for the public REST API. We would like to instead have it available under /activiti-app/api and supporting basic authentication.

     

    To make this happen we need to do just a few changes, here is the updated controller:

     

    package com.activiti.extension.api;

    import com.activiti.extension.rest.GreetingMessage;
    import org.springframework.web.bind.annotation.*;

    @RestController
    @RequestMapping("/enterprise")
    public class HelloWorldRestController {

      @RequestMapping(value = "/hello", method= RequestMethod.GET)
      public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                    defaultValue="World") String name) {
          GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
          return msg;
      }

      @RequestMapping(value = "/hello/{name}", method= RequestMethod.GET)
      public GreetingMessage sayHelloAgain(@PathVariable String name) {
          GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");
          return msg;
      }
    }

     

    The class has been moved into the com.activiti.extension.api package. This package is scanned automatically by Activiti and any components are instantiated as custom REST endpoints in the public REST API, protected by basic authentication. Note that the endpoint needs to have /enterprise as first element in the URL, as this is configured in the SecurityConfiguration to be protected with basic authentication (more specifically, the api/enterprise/* path).

     

    Now, with basic auth available, it is easy to use other clients than the browser, such as Postman, curl etc. We can also in an easy way request JSON responses by setting the Accept header.

     

    Let’s look at a couple of calls using Postman. First we need to set up the Authorization:

     

     

    Then configure the Accept header to application/json to see that we can get responses in JSON format:

     

     

    Now, click Send button with the http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin GET URL specified as above:

     

     

    We can see the JSON response. To have responses in XML, just remove the Accept header:

     

     

    Using curl is as easy:

     

    $ curl -u admin@app.activiti.com:admin http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |xmlstarlet fo

     % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                    Dload  Upload   Total   Spent    Left  Speed

    100   121    0   121    0     0   7330      0 --:--:-- --:--:-- --:--:--  7562

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>

    <greeting>

     <name>Martin</name>

     <text>Hello Martin!</text>

    </greeting>

     

    Here we are piping the XML response into the xmlstarlet tool for formatting. To get JSON responses, do something like this:

     

    $ curl -u admin@app.activiti.com:admin -H "Accept: application/json" http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |jq '.'

     % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                    Dload  Upload   Total   Spent    Left  Speed

    100    40    0    40    0     0   3587      0 --:--:-- --:--:-- --:--:--  3636

    {

     "name": "Martin",

     "text": "Hello Martin!"

    }

     

    In this case we are using the jq command line tool for formatting of the JSON response.

    Injecting Spring Beans into the REST Controller

    So far we have used a REST controller class that was not dependent on any other component. In a real scenario we are likely to have to wire in custom beans and out-of-the-box beans.

     

    Take the following custom Spring Bean:

     

    package com.activiti.extension.bean.service;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;

    @Service
    public class HelloWorldService {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

      public String greeting() {
          logger.info("Custom Service method greeting() was called");
        
          return "Hello World from Service!";
      }
    }

     

    It’s defined in a sub-package to the com.activiti.extension.bean package so it will be picked up by Activiti during scanning and registered as a Spring bean with the helloWorldService name.

     

    We can now wire this bean into our REST Controller as follows:

     

    package com.activiti.extension.api;

    import com.activiti.domain.idm.User;
    import com.activiti.extension.bean.service.HelloWorldService;
    import com.activiti.extension.rest.GreetingMessage;
    import com.activiti.service.api.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    @RestController
    @RequestMapping("/enterprise")
    public class HelloWorldRestController {

      @Autowired
      HelloWorldService helloWorldService;

      @Autowired
      UserService userService;

      @RequestMapping(value = "/hello", method= RequestMethod.GET)
      public GreetingMessage sayHello(@RequestParam(value="name", required=false,
                                                    defaultValue="World") String name) {
          long userId = 1; // Admin
          User user = userService.findUser(userId);
          String username = user.getFirstName() + " " + user.getLastName();

          GreetingMessage msg = new GreetingMessage(name, "Hello " + name +
                    ", service message: " + helloWorldService.greeting() +
                    ", userId = 1 = " + username);

          return msg;
      }

      @RequestMapping(value = "/hello/{name}", method= RequestMethod.GET)
      public GreetingMessage sayHelloAgain(@PathVariable String name) {
          GreetingMessage msg = new GreetingMessage(name, "Hello " + name + "!");

          return msg;
      }
    }

     

    As you can see, we also wire in one of the out-of-the-box services called UserService, and use it to get the first name and last name of the Administrator user.

     

    Making a call via curl now shows the following response, that have involved to external beans:

     

    $ curl -u admin@app.activiti.com:admin -H "Accept: application/json" http://localhost:8080/activiti-app/api/enterprise/hello?name=Martin |jq '.'

     % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                    Dload  Upload   Total   Spent    Left  Speed

    100   116    0   116    0     0   1509      0 --:--:-- --:--:-- --:--:--  1526

    {

     "name": "Martin",

     "text": "Hello Martin, service message: Hello World from Service!, userId = 1 = null Administrator"

    }

     

    The administrator user has no first name, hence it is null.

    Calling a REST endpoint from a Process

    Closely related to implementing a custom REST API is calling out to REST endpoints from a process. This can be done with the out-of-the-box REST call task:

     

     

    Read more about it here and watch this video for a detailed walkthrough. The custom REST call task does not have to be configured to call an external web service, such as a weather service. It can also call back into the Activiti public REST API. This means it could actually call the custom REST API we created in this article.

    Table of Contents                   

     

    Introduction

    The Activiti Enterprise workflow solution supports integration with external databases. It is possible to both read from the database and write to the database during the execution of a process instance.

     

    There are two ways in which you can talk to the external database, via automatically generated CRUD operations or via manually coded CRUD operations (note. DELETE is not currently supported):

     

    In the above picture data is manipulated in table A via custom coded SQL code. This is needed in situations such as when you have a database table that uses a compound primary key, which is not supported in the auto-generated SQL. It is also needed when you for example have a scenario where you make an INSERT that requires you to update another row to be consistent. Table B on the other hand is managed entirely via auto-generated CRUD operations. And there is no need to write any read or write code.

     

    As can be seen in the above picture, there is a specific service task type that should be used when writing to an external database, it is called Store Entity task. Each of these store entity tasks execute in their own transaction, which is important to understand, because if the process instance crashes when executing the “Store Entity B Automatically” task in the above sample, then the database might be in an inconsistent state.

     

    As well as writing data to the database it is also possible to set up a user task form to map form fields to database column values. If the form field is mapped to a primary key then it will automatically trigger a read in the database.

     

    Activiti uses the concept of Entities internally to map between a logical representation of the data and the physical structure of the database table. An entity does not have to map all columns in a table, only those that are used. And if using automatically generated CRUD, then the statements will be generated accordingly. These internal entities are contained in what’s called a Custom Data Model.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your Custom Data Model implementation make sure to set up a proper Activiti Extension project.

    Implementing a process that integrates with an external database

    This section walks you through the steps needed to create a process that uses an external MySQL database to fetch and store data in multiple tables.

     

    The tables that we want to work with looks like this:

     

    mysql> describe employees;

    +------------+---------------+------+-----+---------+-------+

    | Field      | Type          | Null | Key | Default | Extra |

    +------------+---------------+------+-----+---------+-------+

    | emp_no     | int(11)       | NO   | PRI | NULL    |       |

    | birth_date | date          | NO   |     | NULL    |       |

    | first_name | varchar(14)   | NO   |     | NULL    |       |

    | last_name  | varchar(16)   | NO   |     | NULL    |       |

    | gender     | enum('M','F') | NO   |     | NULL    |       |

    | hire_date  | date          | NO   |     | NULL    |       |

    +------------+---------------+------+-----+---------+-------+

    6 rows in set (0.14 sec)

     

    mysql> describe salaries;

    +-----------+---------+------+-----+---------+-------+

    | Field     | Type    | Null | Key | Default | Extra |

    +-----------+---------+------+-----+---------+-------+

    | emp_no    | int(11) | NO   | PRI | NULL    |       |

    | salary    | int(11) | NO   |     | NULL    |       |

    | from_date | date    | NO   | PRI | NULL    |       |

    | to_date   | date    | NO   |     | NULL    |       |

    +-----------+---------+------+-----+---------+-------+

    4 rows in set (0.00 sec)

     

    These tables are part of the MySQL sample employees database that can be downloaded from this site. If you are going to implement this process at the same time as you read this article, then now is the time to download and set up the employees database.

     

    Note. there is currently a bug that prevents tables with PKs that are INTs to be stored correctly. So for example, if you have a PostgreSQL employees table with PK defined as:

    emp_no int primary key not null,

    Then that will not work at the moment, If you have control over the table, alter it so the PK is long.

    Overview

    The process that we will implement will enable a user to update an employee's first name and last name. It will also be possible to set a new salary as active from a specific date. The process definition looks like this:

    The start form will have a field where we can specify the Employee No (the PK into the employees table):

     

     

    When the start form is completed Activiti will automatically read the employee’s current first name and last name from the employees table and make these available as process variables. If the Employee No is not found, then the Entity/process variable will be null.

     

    The current salary will also be fetched but via code we have to write manually. The Update Employee form will then be displayed:

     

     

    The First name and Last name fields are automatically populated. We can then update those if needed. Here we can also specify a new salary and from what date it should be active. When completing this form an Approve Employee Update form is displayed:

     

     

    This form shows suggested new data and the old/current data. Clicking Approved in this form starts the update of the two tables via two separate Store Entity Task nodes. Each one executing in its own transaction. Note that a Store Entity Task node can manage only one entity.

     

    We will see logging as follows from the custom data model service implementation:

     

    08:30:59,898 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() EntityDefinition [Name=Salary][TableName=salaries][Id=null][Attributes=4] [fieldName=Employee No] [variableValue=10001]

    08:30:59,899 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() Response: {"Employee No":10001,"From Date":"2002-06-22T00:00:00Z","To Date":"2016-12-01T00:00:00Z","Salary":88958}

    08:30:59,938 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() EntityDefinition [Name=Salary][TableName=salaries][Id=null][Attributes=4] [fieldName=Employee No] [variableValue=10001]

    08:30:59,940 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() Response: {"Employee No":10001,"From Date":"2002-06-22T00:00:00Z","To Date":"2016-12-01T00:00:00Z","Salary":88958}

    08:31:00,023 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() EntityDefinition [Name=Salary][TableName=salaries][Id=null][Attributes=4] [fieldName=Employee No] [variableValue=10001]

    08:31:00,024 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - getMappedValue() Response: {"Employee No":10001,"From Date":"2002-06-22T00:00:00Z","To Date":"2016-12-01T00:00:00Z","Salary":88958}

    08:31:00,024 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.CustomDataModelServiceImpl  - storeEntity() EntityDefinition [Name=Salary][TableName=salaries][Id=null][Attributes=4] [dataModel=Employee Custom] [attributeDefinitionsAndValues=4]

    Tell Activiti about the external database

    Before we can do anything with external database tables we need to first configure Activiti with connection parameters for it. So it can do a basic JDBC connection to it. This is done via the Identity Management application (note. you need to be logged in as admin):

     

     

    Click on this application so you see this screen:

     

     

    Now click on the Tenants menu item, this takes you to this screen:

     

     

    Here click on Data sources followed by a click on the + sign to the right to add a new data source:

     

    Now, set up the connection parameters for your MySQL employees database. Then click Save. This database configuration can now be used by Activiti when doing the automatic CRUD operations. And it is also used when mapping the entities that will be automatically managed (i.e. the employees table entity).

     

    Note. if you are only going to use manually managed entities, such as we plan to do with the salaries table, then you don’t need to set up this data source.

    Download and Install the JDBC driver

    Whether we use automatically managed entities, like we plan on doing for the employees table, or manually managed entities, like for the salaries table. We always need to download and install the JDBC driver that will be used to talk to the database. This driver will be used both by Activiti auto generated SQL statements and our custom coded SQL statements.

     

    Download for example the mysql-connector-java-5.1.40-bin.jar file and put it in the <activiti-install-dir>/tomcat/lib directory.

    Creating Custom Data Models and Logical Entities

    Before we start creating any process model we need to first sort out the logical entities that we will be using in the process model. They will live in a custom data model. As we are going to use both an automatically managed entity (mapping to the employees table) and a manually managed entity (mapping to the salaries table), it will be required to have one data model per entity. The reason for this is that a data model can only contain either automatically managed entities, referred to as Database Entities, or manually managed entities, referred to as Custom Entities.

    Creating the Data Model and Database Entity for the employees table

    Let start with the Database entity for the employees table. Click on the Kickstart App application:

     

     

    This displays the following screen:

     

     

    Then click on the Data Models menu item:

     

     

    Now, click on the Create Data Model button in the upper right corner:

     

     

    Enter the name and description for the data model and then click the Create new data model button:

     

     

    First select the Data source (the one we added earlier on). Then click Add entity to start adding the logical entity that should map to the employees table. Give the entity a logical name, such as Employee and then specify what database table it maps to, in this case employees. Then start mapping the columns in the table.

     

    In the above picture we can just see the primary key column mapping. The Required property needs to be checked as the PK does not accept null values. Note also that the Database generated value property doesn't need to be checked since the employees table does not have the emp_no PK set as AUTO_INCREMENT.

     

    Add also the following column mappings:

     

    For first name:

     

    And for last name:

     

    And finally the gender column:

     

    Note that we have not mapped all columns in the employees table. This is because Activiti currently have a problem with date columns. We will only read and update the emp_no, first_name, last_name, and gender columns in this example. If you need to read and write all columns in a table that has a compound PK, or date columns, then use a Custom Entity instead as we will with the salaries table.

     

    Note. if you will be using these entity attribute names when configuring visibility conditions in a form, or in sequence flow conditions, then using spaces in the Attribute name might cause problems, see this jira, and this one.

    Creating the Data Model and Custom Entity for the salaries table

    Click on the Create Data Model button:

     

    Enter the name and description for the data model and then click Create new data model button:

     

     

    Now, we are going to manage the CRUD operations for this entity manually, with code we write. So to enable this click the Custom button in the upper left side. This disables the automatic CRUD operations, and the link to the Data source, and sets this entity up to be managed via an implementation of the com.activiti.api.datamodel.AlfrescoCustomDataModelService interface. The implementation class will be called every time that an Activity needs to read or write a Salary entity.

     

    We add the attributes that should make up the logical entity in the same way we did for the Employee entity, only difference is that we don’t map them to a table column as was needed when CRUD operations should be created automatically. But we need to match to what we want to store in the salaries table. Continue with the other attributes, first the from and to dates:

     

    Then the Salary attribute:

     

    This finishes the custom data model definitions. We are now ready to start implementing the process model.

    Configuring form and data model mappings for the Start Task

    The start form for the start task will be used to collect the unique ID for the employee that should be updated:

     

     

    This form is very straightforward with a single number field.

     

    The start task also need to have a mapping of this form field to some entity fields:

     

     

    Click on the Form field to data model mapping property:

     

    Start by mapping the Employee No form field to the Employee entity and its Employee No attribute, which is mapped to the PK in the employees table. Call the process variable that will contain the automatically fetched Employee entity selectedEmployee. We will then be able to refer to this process variable object in future tasks/nodes.

     

    Then map the Employee No form field again to the Salary entity and its Employee No attribute, which will be part of the PK to lookup the current salary for the employee in the code that we will write (we will also use current date to compare against from and to date to find current salary):

     

     

    That completes the Start Task.

    Configuring the form for the Update Employee Task

    The form for the update employee task will be used to update the employee's name and salary:

     

     

    The form starts with a Display value field at the top that will show currently selected employee no. Then we got two Text fields for updating first and last name. And at the bottom we got a Number field for updating the salary and a Date field to specify from when this new salary should be active. There are also two headers that are not necessary but makes it look a bit nicer.

     

    To have the top field show the Employee No for the selected employee we have to configure it a bit:

     

     

    Click on the Variable button to the right, this makes all the process variables available, including the auto fetched employee entity. Select to display the selectedEmployee.Employee No attribute of the entity. We can see that the currentSalary entity is also available, but it will not work until we have the backing SkyVaultCustomDataModelService implemented.

     

    What we would like also to happen when this form is displayed is that the first and last name fields should be pre-populated with current values. Just before you open a form that you are working on, you should see a screen looking something like this:

     

     

    Instead of clicking on the Open button click on the Map Variables > button:

     

    Here we can configure what value should be used to pre-populate a form field, and what process variable should contain the form field value when the form is completed. For example, for the First Name field we want it to be pre-populated with the selectedEmployee.First Name source process variable. And when the form is completed we want a new target process variable called newFirstName to contain the value from the form field. We can configure the last name field in the same way.

     

    This completes the Update Employee task.

    Configuring the form for the Approve Employee Update Task

    The Approve Employee task should just show the approver what new names and salary that have been suggested. The approver then clicks Approve to trigger the update to the database, or reject to cancel the update. The form looks like this:

     

    This form basically contains a bunch of Display value fields that shows all the updated employee data for the approver. The newFirstName, newLastName, New Salary, and New From Date are all form field values. The others are displayed from variables.

     

    For example, newFirstName is configured as follows:

     

     

    And an example of a Variable display looks like this:

     

    When we have finished configuring all the fields we need to also configure the Approved and Rejected outcomes as follows:

     

    Now, while we are at it, Save the form and configure the Approve sequence flow condition as follows:

     

     

     

    And then the Rejected flow condition as follows:

     

     

    This completes the Approve Employee Update task and the following sequence flows.

    Configuring the Update Employee Table Store Entity Task

    Now we are ready to store the updated employee information, let’s start with the Employee entity, which will be automatically saved to the employees table via our Date Model database entity mapping.

     

    We only need to do a little bit of configuration for this to work, click on the Update Employee Table store entity task:

     

     

    Then click on the Attribute mapping property for the store entity task:

     

     

    Basically what we need to do here is configure what values we want to use when updating (or creating a new row). First we need to however select the custom data model, which is Employee DB for the automatically managed Employee entity. Then select the Employee entity (there is only one entity defined in the data model so far). Then we click on the Existing variable button as we are doing an UPDATE. And then select what process variable contains existing data such as PK, select selectedEmployee.

     

    Now configure/map each Entity Attribute to the value that should be used in the update. We use the existing PK and Gender values, but pick the new first name and last name, which would be form fields when we pick them to the right.

     

    That’s it for this Store Entity Task.

    Configuring the Update Salary Table Store Entity Task

    The last Store Entity task will be used to store the updated salary for the employee. Storing this entity is going to be managed by us manually, we need to do some coding for it to work.

     

    But let’s first configure the Attribute mappings so we are clear on what we want to store:

     

     

    First select the custom data model, which is Employee Custom for the manually managed Salary entity. Then select the Salary entity (there is only one entity defined in this data model too). Then we click on Existing variable button as we are using values from an existing salary record, basically just the Employee No. And then select what process variable contains existing data such as PK, select currentSalary.

     

    Now configure/map each Entity Attribute to the value that should be used in the insert (we are going to create a new entry in the salaries table). We use the existing PK, but pick the new salary and from date, which would be form fields when we pick them to the right. For the to date we want it to be indefinitely until a new salary is added, so we choose Static value to the right and enter ‘9999-01-01’.

     

    That’s it for this Store Entity Task. It will not work though until we have implemented next section.

    Add code to handle read and write of the custom Salary entity

    Now, we have opted to handle the read and write of Salary entities to the database manually. That’s why this entity is configured as a Custom Entity and not mapped to a specific database table in the custom data model.

     

    When the Activiti engine needs to read or write a Custom Entity it will call an implementation of the com.activiti.api.datamodel.AlfrescoCustomDataModelService interface. So we need to supply one.

     

    Here is one implementation that uses the Spring JDBC template, which is good as it is thread safe and takes care of closing resources etc:

     

    package com.activiti.extension.bean;

    import com.activiti.api.datamodel.SkyVaultCustomDataModelService;
    import com.activiti.model.editor.datamodel.DataModelDefinitionRepresentation;
    import com.activiti.model.editor.datamodel.DataModelEntityRepresentation;
    import com.activiti.runtime.activiti.bean.datamodel.AttributeMappingWrapper;
    import com.activiti.variable.VariableEntityWrapper;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import org.apache.commons.dbcp.BasicDataSource;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
    import org.springframework.stereotype.Service;

    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    @Service
    public class CustomDataModelServiceImpl implements SkyVaultCustomDataModelService {
      private static Logger logger = LoggerFactory.getLogger(CustomDataModelServiceImpl.class);

      /**
       * Database table names
       */

      private static final String SALARIES_TABLE_NAME = "salaries";

      /**
       * Mapping entity into JSON
       */

      @Autowired
      protected ObjectMapper objectMapper;

      /**
       * Use Spring JDBC Template for database access
       */

      private SimpleJdbcTemplate jdbcTemplate;

      /**
       * Salary Data Transfer Object (DTO)
       */

      private class Salary {
          private long empNo;
          private Date fromDate;
          private Date toDate;
          private long salary;

          public long getEmpNo() { return empNo; }
          public void setEmpNo(long empNo) { this.empNo = empNo; }
          public Date getFromDate() { return fromDate; }
          public void setFromDate(Date fromDate) { this.fromDate = fromDate; }
          public Date getToDate() { return toDate; }
          public void setToDate(Date toDate) { this.toDate = toDate; }
          public long getSalary() { return salary; }
          public void setSalary(long salary) { this.salary = salary; }
      }

      /**
       * Salary Spring JDBC Row Mapper
       */

      class SalaryRowMapper implements RowMapper {
          public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
              Salary salary = new Salary();
              salary.setEmpNo(rs.getLong("emp_no"));
              salary.setFromDate(rs.getDate("from_date"));
              salary.setToDate(rs.getDate("to_date"));
              salary.setSalary(rs.getLong("salary"));

              return salary;
          }
      }

      /**
       * CREATE USER employees@localhost IDENTIFIED BY '1234';
       * GRANT ALL PRIVILEGES ON employees.* TO employees@localhost IDENTIFIED BY '1234';
       * FLUSH PRIVILEGES;
       */

      public CustomDataModelServiceImpl() {
          BasicDataSource ds = new BasicDataSource();
          ds.setDriverClassName("com.mysql.jdbc.Driver");
          ds.setUrl("jdbc:mysql://localhost:3306/employees");
          ds.setUsername("employees");
          ds.setPassword("1234");

          jdbcTemplate = new SimpleJdbcTemplate(ds);
      }

      /**
       * This method is called when Activiti wants to fetch a row from the database table
       * that has been mapped as a "Custom" entity.
       *
       * @param entityDefinition the definition of the "custom" entity that was mapped in the Custom Data Model (e.g. Salary)
       * @param fieldName the entity field that represents the PK (e.g. Employee No)
       * @param fieldValue the entity field value (e.g. 10001)
       * @return an object representing the fetched data
       */

      @Override
      public ObjectNode getMappedValue(DataModelEntityRepresentation entityDefinition,
                                       String fieldName, Object fieldValue) {
          logger.info("getMappedValue() EntityDefinition [Name=" + entityDefinition.getName() +
                  "][TableName=" + SALARIES_TABLE_NAME +
                  "][Id=" + entityDefinition.getId() +
                  "][Attributes=" + entityDefinition.getAttributes().size() +
                  "] [fieldName=" + fieldName +
                  "] [variableValue=" + fieldValue + "]");

          // Check if are to get something from the salaries table
          if (StringUtils.equals(entityDefinition.getTableName(), SALARIES_TABLE_NAME)) {
              // Fetch the Salary row we are looking for
              Long employeeNo = (Long) fieldValue;
              Date currentDate = new Date();

              String sql = "SELECT * FROM " + SALARIES_TABLE_NAME +
                      " WHERE emp_no = ? and from_date <= ? and to_date > ?";

              Salary salary = (Salary) jdbcTemplate.queryForObject(
                      sql, new SalaryRowMapper(), new Object[]{employeeNo, currentDate, currentDate});

              // The following fields have to match the attributes set up in the Custom Data Model
              // and what is used in the form that will display them
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // The expected entity date format is ISO-8601
              ObjectNode fetchedSalaryRowAsJSON = objectMapper.createObjectNode();
              fetchedSalaryRowAsJSON.put("Employee No", salary.getEmpNo());
              fetchedSalaryRowAsJSON.put("From Date", sdf.format(salary.getFromDate()) + "Z"); // see https://github.com/FasterXML/jackson-databind/issues/338
              fetchedSalaryRowAsJSON.put("To Date", sdf.format(salary.getToDate()) + "Z");
              fetchedSalaryRowAsJSON.put("Salary", salary.getSalary());

              logger.info("getMappedValue() Response: " + fetchedSalaryRowAsJSON.toString());

              return fetchedSalaryRowAsJSON;
          }

          return null;
      }

      @Override
      public VariableEntityWrapper getVariableEntity(String keyValue, String variableName,
                                                     String processDefinitionId,
                                                     DataModelEntityRepresentation entityValue) {
          logger.info("getVariableEntity() Entity [Name=" + entityValue.getName() +
                  "][TableName=" + SALARIES_TABLE_NAME + "][Id=" + entityValue.getId() +
                  "][Attributes=" + entityValue.getAttributes().size() +
                  "] [keyValue=" + keyValue +
                  "] [variableName=" + variableName + "]");

          return null;
      }

      /**
       * This method is called when Activiti wants to store a "Custom" entity in the
       * database.
       *
       * @param attributeDefinitionsAndValues attributes that will become the column values
       * @param entityDefinition the definition of the "custom" entity that was mapped in the Custom Data Model (e.g. Salary)
       * @param dataModel the custom data model that contains the "custom" entity definition
       * @return
       */

      @Override
      public String storeEntity(List<AttributeMappingWrapper> attributeDefinitionsAndValues,
                                DataModelEntityRepresentation entityDefinition,
                                DataModelDefinitionRepresentation dataModel) {
          logger.info("storeEntity() EntityDefinition [Name=" + entityDefinition.getName() +
                  "][TableName=" + SALARIES_TABLE_NAME + "][Id=" + entityDefinition.getId() +
                  "][Attributes=" + entityDefinition.getAttributes().size() +
                  "] [dataModel=" + dataModel.getName() +
                  "] [attributeDefinitionsAndValues=" + attributeDefinitionsAndValues.size() + "]");

          // Check if we are to store something in the salaries table
          if (StringUtils.equals(entityDefinition.getTableName(), SALARIES_TABLE_NAME)) {
              // Set up a map of all the column names and values
              Map<String, Object> parameters = new HashMap<String, Object>();
              for (AttributeMappingWrapper attributeMappingWrapper : attributeDefinitionsAndValues) {
                  // Get the column name = mapped name
                  // And the column value = attr value
                  parameters.put(attributeMappingWrapper.getAttribute().getMappedName(),
                          attributeMappingWrapper.getValue());
              }

              // Update current salary entry to previous
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
              String sql = "UPDATE " + SALARIES_TABLE_NAME +
                      " SET to_date = '" +  sdf.format(parameters.get("from_date")) + "'" +
                      " WHERE emp_no = " + parameters.get("emp_no") +
                      " AND to_date = '9999-01-01'";
              jdbcTemplate.update(sql);

              // Insert new salary entry
              sql = "INSERT INTO " + SALARIES_TABLE_NAME +
                      " (emp_no, from_date, to_date, salary) VALUES (:emp_no, :from_date, :to_date, :salary)";
              jdbcTemplate.update(sql, parameters);
          }

          return null;
      }
    }

     

    Note that this class is in the automatically scanned package com.activiti.extension.bean so it will be picked up as a bean as it is annotated with @Service.

     

    The getMappedValue() method is called by Activiti when it wants to read and populate a Salary entity, or any other custom entity, so we need to check that it wants to fetch a Salary entity. Passed in is primary key information in the fieldValue. Because the salaries table have a compound PK we use current date to compare with from_to and to_date to get one row in the response. There is also, as mentioned, some date problems that we work around.

     

    When Activiti wants to store a Salary entity (i.e. when we get to the Update Salary Table task) it will call the storeEntity() method. It will call this method for all custom entities that should be stored, so we need to check that it is the Salary entity that should be stored. Here we do a bit more and also update current latest salaries entry before inserting a new row. So this is quite useful, we can do more than one operation when a store is initiated, we have full control.

     

    For this to build you need to also add the following dependency in the extension project pom.xml:

     

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
      <scope>provided</scope>
    </dependency>

    Table of Contents                   

     

    Introduction

    Listeners are an Activiti extension to the BPMN 2.0 standard that implement hook points inside a process definition. These are triggered by events during workflow execution. There are two type of listeners, task and execution.

     

    Execution listeners can be configured on the process itself, as well as on activities and on transitions. Task listeners can only be configured on user tasks.

     

    Listeners enable you to run code during the execution of the process instance. This can be script code or a call to a Java class. The following diagram shows the events in a process definition where you can configure a listener:

     

    This picture also illustrates the order in which the event listeners will be invoked. Note for example that an assign task listener is invoked before a create task listener, which might be a bit surprising.

     

    Listeners are described in more detail in the Activiti user guide, see the execution listeners docs and the task listeners docs.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your listener implementation make sure to set up a proper Activiti Extension project.

    Implementing Java backed Listeners

    Let’s implement one execution listener and one task listener in Java and configure them for all the events shown for the process definition in the introduction section. The listeners will not do much more than print a log message.

    Coding the Java classes

    This section goes through how to implement both the execution and the task listener.

    Implementing the Execution Listener

    Here is the implementation of the Execution Listener class:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.ExecutionListener;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class HelloWorldExecutionListener implements ExecutionListener {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldExecutionListener.class);

      @Override
      public void notify(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][event=" + execution.getEventName() +
                      "][ExecutionListener=" + this +
                      "][ActivityId=" + execution.getCurrentActivityId() + "]");
      }
    }

     

    The class has been created in the com.activiti.extension.bean package, but it does not have to be located in this package. It is just good practice to put the class in this package if it ever needs to be converted to a Spring Bean backed listener in the future. Then this package is scanned automatically by Activiti and any beans are instantiated.

     

    An Execution Listener needs to implement the org.activiti.engine.delegate.ExecutionListener interface, which contains the notify method. This methods takes one parameter of type org.activiti.engine.delegate.DelegateExecution, which is how we get access to the process instance that is invoking this Listener. An Execution Listener object instance is shared between process instances, so the DelegateExecution parameter is our way of finding out information about the active process instance.

     

    Notice how we use an external class here for logging and that the logger.info method could be called concurrently by multiple threads. So it needs to be thread safe, which it is. Same thing applies for other class members that we define and use in the notify method. We can do whatever we want inside the notify method as all threads have independent call stacks.

    Implementing the Task Listener

    Here is the implementation of the Task Listener class:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.DelegateTask;
    import org.activiti.engine.delegate.TaskListener;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class HelloWorldTaskListener implements TaskListener {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldTaskListener.class);

      @Override
      public void notify(DelegateTask task) {
          DelegateExecution execution = task.getExecution();

          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][event=" + task.getEventName() +
                      "][TaskListener=" + this +
                      "][ActivityId=" + execution.getCurrentActivityId() +
                      "][TaskAssignee=" + task.getAssignee() +
                      "][TaskForm=" + task.getFormKey() + "]");
      }
    }

     

    The class has been created in the com.activiti.extension.bean package, but it does not have to be located in this package. It is just good practice to put the class in this package if it ever needs to be converted to a Spring Bean backed listener in the future. Then this package is scanned automatically by Activiti and any beans are instantiated.

     

    A Task Listener needs to implement the org.activiti.engine.delegate.TaskListener interface, which contains the notify method. This methods takes one parameter of type org.activiti.engine.delegate.DelegateTask, which is how we get access to the user task instance that is invoking this Listener. We can also get to the process execution via the task.getExecution method.

     

    Via the passed in DelegateTask object you can also get to more details around the user task, such as who it is assigned to, its description, event, when it was created, what form it is using etc.

     

    A Task Listener object instance is shared between process instances, so the DelegateTask parameter is our way of finding out information about the active process instance and user task instance.

     

    Note. Task Listeners work only on User tasks, they will have no effect on for example a Service Task.

    Testing the Listeners

    Now to test the Listener implementations create a process model looking like this:

     

     

    Then set up the listeners as follows, start by clicking on the canvas (not on any activity or event) so you can set the process level execution listener:

     

     

    Clicking on the No execution listeners configured property brings up the following dialog:

     

     

    Add the com.activiti.extension.bean.HelloWorldExecutionListener implementation for the start and end events. Then click on the User Task 1 activity:

     

     

    Clicking on the No execution listeners configured property brings up the following dialog where we can set up listener for start and end event for activity User Task 1:

     

     

    Save the execution listener configuration and then click on the task listeners property:

     

    This bring up a dialog where we can configure task listeners for the different events:

     

    Here we are using the com.activiti.extension.bean.HelloWorldTaskListener implementation. We can also see a new task event called delete that we can attach a task listener to, it will be invoked when the task instance is completed and deleted.

     

    Now, for the sequence flow between the User Task 1 and the Simple Script Task set up an execution listener on the take event. First, click on the transition:

     

     

    Then click on the No execution listeners configured property, which brings up the Execution Listener configuration dialog:

     

     

    Use the com.activiti.extension.bean.HelloWorldExecutionListener implementation for the take event.

     

    For the Simple Script Task set up execution listeners for the start and end event:

     

     

    The configuration will look like this:

     

     

    We also need to configure a script and script format for the Script task as it currently is failing validation:

     

     

    Add a simple Groovy script that just prints a log message:

     

     

    In BPMN 2.0 XML this will look like:

     

    <process id="TestingListeners" name="Testing Listeners" isExecutable="true">
      <extensionElements>
          <activiti:executionListener event="start"
              class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

          <activiti:executionListener event="end"
              class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

      </extensionElements>

      <startEvent id="startEvent" name="Start Event"></startEvent>

      <sequenceFlow id="transitionStart2UserTask"
                    sourceRef="startEvent"
                    targetRef="userTask1">
    </sequenceFlow>

      <userTask id="userTask1" name="User Task 1" activiti:assignee="$INITIATOR">
          <extensionElements>
              <activiti:executionListener event="start"
                  class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

              <activiti:executionListener event="end"
                  class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

              <activiti:taskListener event="create"
                  class="com.activiti.extension.bean.HelloWorldTaskListener"/>

              <activiti:taskListener event="assignment"
                  class="com.activiti.extension.bean.HelloWorldTaskListener"/>

              <activiti:taskListener event="complete"
                  class="com.activiti.extension.bean.HelloWorldTaskListener"/>

              <activiti:taskListener event="delete"
                  class="com.activiti.extension.bean.HelloWorldTaskListener"/>

          </extensionElements>
      </userTask>

      <sequenceFlow id="transitionUserTask2ScriptTask"
                    sourceRef="userTask1"
                    targetRef="scriptTask">

          <extensionElements>
              <activiti:executionListener event="take"
                  class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

          </extensionElements>
      </sequenceFlow>

      <scriptTask id="scriptTask" name="Simple Script Task"
                  scriptFormat="groovy"
                  activiti:autoStoreVariables="false">

          <extensionElements>
              <activiti:executionListener event="start"
                  class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

              <activiti:executionListener event="end"
                  class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

          </extensionElements>
          <script><![CDATA[print "Hello from Script Task"]]></script>
      </scriptTask>

      <sequenceFlow id="transitionScriptTask2EndEvent"
                    sourceRef="scriptTask"
                    targetRef="endEvent">
    </sequenceFlow>

      <endEvent id="endEvent"></endEvent>
    </process>

     

    Note that the process definition has been updated so each element has a more descriptive ID. This will make it easier to see what is going on when we do logging. It is also easier to read the process definition.

     

    When we start this process it will print similar logs to the following:

     

    08:01:52,361 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@47c496c6][ActivityId=startEvent]

    08:01:52,368 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@4dcb7e48][ActivityId=userTask1]

    08:01:52,378 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132501][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@3df0a8c4][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    08:01:52,379 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132501][event=create][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@5cac7332][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

     

    Here we can see that the execution listener set on the process level is invoked first when we get the process start event. Then we get the execution listener invoked again on the task start event. Then it moves on with task assignment and create event, invoking the task listener. All as expected. What might be unexpected is that there is actually one listener instance created for each event it is registered on. So here we have four unique listener objects (i.e. 47c496c6, 4dcb7e48, 3df0a8c4, and 5cac7332).

     

    When we complete the User Task 1 the following logs are printed:

     

    08:08:01,566 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132501][event=complete][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@1d06918c][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    08:08:01,567 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132501][event=delete][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@59681d0f][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    08:08:01,573 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=end][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@1687fdbb][ActivityId=userTask1]

    08:08:01,577 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=take][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@4e282041][ActivityId=userTask1]

    08:08:01,578 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@7224678f][ActivityId=scriptTask]

    08:08:02,281 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=end][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@65230b79][ActivityId=scriptTask]

    08:08:02,282 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132501][event=end][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@24183909][ActivityId=endEvent]

     

    As expected, we can see the task complete and delete events, followed by the end of user task. Then we get the invocation of the execution listener for the take event on the transition. Then we get the execution listener invoked for the start and end event of the script task activity. And finally the end event for the whole process.

     

    Note again that each registration of a listener will get its own listener instance during runtime. Let’s see what happens if we start two processes in parallel, without completing the User task. Here is the logs from process one:

     

    08:13:43,809 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132509][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@47c496c6][ActivityId=startEvent]

    08:13:43,811 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132509][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@4dcb7e48][ActivityId=userTask1]

    08:13:43,812 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132509][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@3df0a8c4][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    08:13:43,812 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132509][event=create][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@5cac7332][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

     

    And then we start the second process:

     

    08:15:00,808 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132515][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@47c496c6][ActivityId=startEvent]

    08:15:00,809 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=132515][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@4dcb7e48][ActivityId=userTask1]

    08:15:00,810 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132515][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@3df0a8c4][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    08:15:00,811 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=132515][event=create][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@5cac7332][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

     

    Important thing to be aware of here is that listener instances will be re-used between processes.

    Listeners and Thread Safety

    When using execution and task listeners, things are not thread safe even if each event the listener is registered for results in a unique listener instance. This is because listener instances are shared between process instances. This could potentially lead to library code used in the listener implementation being called concurrently, and as a result it needs to be thread-safe, as the log library we used in our example. Same thing for any class member variables, access needs to be synchronized.

     

    So use thread-safe libraries and avoid class members.

    Using Process Variables in a Listener

    Most of the time when you implement a listener you would want to use some process variable values. Process variables are typically set when the process instance is started, through the API, or by different activities in the process. They are stored in the database for each process instance.

     

    So how do you pass these process variable values into the listener? It is not necessary to pass any value, you can access all process instance variables via the DelegateExecution object, or the DelegateTask object, and the getVariable method.

     

    What variables that are available in a listener depends on if it is an execution listener or a task listener. So let’s talk about them separately.

    Using Process Variables in an Execution Listener

    This is very similar to how you read and write process variables in a Java Delegate, read the docs here.

     

    Let’s say we got the following execution listener implementation:

     

    public void notify(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][event=" + execution.getEventName() +
                  "][ExecutionListener=" + this +
                  "][ActivityId=" + execution.getCurrentActivityId() + "]");

      String initiator = (String)execution.getVariable("initiator");
      logger.info("Initiator of the process has user ID = " + initiator);

      execution.setVariable("greeting1Proc", "Hello World!");
      execution.setVariableLocal("greeting1ProcLocal", "Hello World Local!");

      logger.info("--- Process variables:");
      Map<String, Object> procVars = execution.getVariables();
      for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
          logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
      }
    }

     

    It will result in the following log messages if we look at for example the output from the execution listener attached to the process start event:

     

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=140005][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@25c1060a][ActivityId=startEvent]

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - Initiator of the process has user ID = 1

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - --- Process variables:

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1Proc = Hello World!]

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1ProcLocal = Hello World Local!]

    09:10:02,994 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [initiator = 1]

    Using Process Variables in a Task Listener

    When it comes to using process variables in a task listener we have to take into account a new scope. The user task is created in a sub-scope to the process instance. So if we got process variables, will they get propagated to the task scope, yes they will, automatically.

     

    Take the following task listener implementation:

     

    public void notify(DelegateTask task) {
      DelegateExecution execution = task.getExecution();

      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][event=" + task.getEventName() +
                  "][TaskListener=" + this +
                  "][ActivityId=" + execution.getCurrentActivityId() +
                  "][TaskAssignee=" + task.getAssignee() +
                  "][TaskForm=" + task.getFormKey() + "]");

      String initiator = (String)execution.getVariable("initiator");
      logger.info("Initiator of the process has user ID = " + initiator);

      execution.setVariable("greeting2Proc", "Hello World!");
      execution.setVariableLocal("greeting2ProcLocal", "Hello World Local!");
      task.setVariable("greetingTask", "Hello World!");
      task.setVariableLocal("greetingTaskLocal", "Hello World Local!");

      logger.info("--- Process variables:");
      Map<String, Object> procVars = execution.getVariables();
      for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
          logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
      }

      logger.info("--- Task variables:");
      Map<String, Object> taskVars = task.getVariables();
      for (Map.Entry<String, Object> taskVar : taskVars.entrySet()) {
          logger.info("   [" + taskVar.getKey() + " = " + taskVar.getValue() + "]");
      }
    }

     

    This will print a log looking something like this for the user task assignment event:

     

    09:10:03,006 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - [Process=140005][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldTaskListener@62afbe29][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    09:10:03,006 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - Initiator of the process has user ID = 1

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - --- Process variables:

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting1Proc = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting1ProcLocal = Hello World Local!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting2Proc = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [initiator = 1]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greetingTask = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting2ProcLocal = Hello World Local!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  - --- Task variables:

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting1Proc = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting1ProcLocal = Hello World Local!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting2Proc = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greetingTaskLocal = Hello World Local!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [initiator = 1]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greetingTask = Hello World!]

    09:10:03,008 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldTaskListener  -    [greeting2ProcLocal = Hello World Local!]

     

    Let’s start with process scoped variables, we can see that we got all the variables we set available in the process scope, including variables such as greeting1Proc that has been set by previous execution listeners and variables such as greetingTask that has been set in task scope. However, there is one variable missing, the greetingTaskLocal. This is because the task instance is created in a sub-scope to the process scope, and when you use task.setVariableLocal("greetingTaskLocal", "Hello World Local!"), it will set this variable only in the task scope. A local task variable will not be propagated to the process scope and it will be gone when the task instance is deleted.

     

    Now let’s see what variables we got available in the task scope, the “task variables”. We can see that we got all the process scoped variables (i.e. greeting1Proc, greeting2Proc, greeting1Local, and greeting2Local) as they are automatically propagated through to the task scope when the task instance is created. Besides the greetingTask variable we can also see the local task variable greetingTaskLocal.

     

    Now let’s see what process variables will be available when we get to the script task. Let’s complete the user task and we should then see the following variables when we enter the start event for the script task:

     

    09:30:21,630 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - [Process=140005][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldExecutionListener@5b12a6e][ActivityId=scriptTask]

    09:30:21,630 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - Initiator of the process has user ID = 1

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  - - Process variables:

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1Proc = Hello World!]

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting1Local = Hello World Local!]

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting2Proc = Hello World!]

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [initiator = 1]

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greetingTask = Hello World!]

    09:30:21,633 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldExecutionListener  -    [greeting2ProcLocal = Hello World Local!]

     

    We can see that we got pretty much all variables we have set available, the only one that is not available any more is the greetingTaskLocal as it was set locally in the user task scope. The reason we can see the greetingTask variable here is that when it was set in the task scope with task.setVariable, this method will actually navigate up the scope hierarchy until it reaches a root scope and set it there, or overwrite any local variable with the same name on the way.

    Using Field Injection in a Listener

    This works in the same way as for service task POJO Java Delegates. See docs.

    Making the Listener a Spring Bean

    This works in the same way as for service task Spring Bean Java Delegates. See docs.

     

    A “springified” execution listener looks like this:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.ExecutionListener;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    import java.util.Map;

    @Component("helloWorldExecutionListener")
    public class HelloWorldSpringExecutionListener implements ExecutionListener {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringExecutionListener.class);

      @Override
      public void notify(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][event=" + execution.getEventName() +
                      "][ExecutionListener=" + this +
                      "][ActivityId=" + execution.getCurrentActivityId() + "]");
      }
    }

     

    And it is used as in the following example:

     

     

    And the BPMN 2.0 XML for this will look like:

     

    <sequenceFlow id="transitionStart2UserTask" sourceRef="startEvent" targetRef="userTask1">
      <extensionElements>
          <activiti:executionListener
             event="take"
             delegateExpression="${helloWorldExecutionListener}"/>

      </extensionElements>
    </sequenceFlow>

     

    And in a similar way we can easily turn a task listener into a Spring bean:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.DelegateTask;
    import org.activiti.engine.delegate.TaskListener;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    import java.util.Map;

    @Component("helloWorldTaskListener")
    public class HelloWorldSpringTaskListener implements TaskListener {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringTaskListener.class);

      @Override
      public void notify(DelegateTask task) {
          DelegateExecution execution = task.getExecution();

          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][event=" + task.getEventName() +
                      "][TaskListener=" + this +
                      "][ActivityId=" + execution.getCurrentActivityId() +
                      "][TaskAssignee=" + task.getAssignee() +
                      "][TaskForm=" + task.getFormKey() + "]");
      }
    }

     

    Note. as mentioned before, these classes need to be located in the com.activiti.extension.bean package to be picked up by Activiti bean scanning.

     

    And it is used as in the following example, click on the User task and then on the task listeners property:

     

     

    And the BPMN 2.0 XML for this will look like:

     

    <userTask id="userTask1" name="User Task 1" activiti:assignee="$INITIATOR">
      <extensionElements>
          <activiti:executionListener event="start"
              class="com.activiti.extension.bean.HelloWorldExecutionListener"/>

          <activiti:executionListener event="end"
              class="com.activiti.extension.bean.HelloWorldExecutionListener"/>


          <activiti:taskListener event="create"
              delegateExpression="${helloWorldTaskListener}"/>


          <activiti:taskListener event="assignment"
              class="com.activiti.extension.bean.HelloWorldTaskListener"/>

          <activiti:taskListener event="complete"
              class="com.activiti.extension.bean.HelloWorldTaskListener"/>

          <activiti:taskListener event="delete"
              class="com.activiti.extension.bean.HelloWorldTaskListener"/>

      </extensionElements>
    </userTask>

     

    If we were to change all our listeners that we have configured for the process into Spring bean based ones. Then we would have logs looking like this. Starting process one, not completing user task 1:

     

    10:37:09,980 [http-nio-8080-exec-8] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142551][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=startEvent]

    10:37:09,981 [http-nio-8080-exec-8] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142551][event=take][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=startEvent]

    10:37:09,981 [http-nio-8080-exec-8] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142551][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=userTask1]

    10:37:09,982 [http-nio-8080-exec-8] INFO  com.activiti.extension.bean.HelloWorldSpringTaskListener  - [Process=142551][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldSpringTaskListener@6da401df][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    10:37:09,988 [http-nio-8080-exec-8] INFO  com.activiti.extension.bean.HelloWorldSpringTaskListener  - [Process=142551][event=create][TaskListener=com.activiti.extension.bean.HelloWorldSpringTaskListener@6da401df][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

     

    If we start a second process instance and stop before completing User task 1:

     

    10:40:14,691 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142557][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=startEvent]

    10:40:14,692 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142557][event=take][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=startEvent]

    10:40:14,692 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=142557][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@2a72c0a6][ActivityId=userTask1]

    10:40:14,693 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldSpringTaskListener  - [Process=142557][event=assignment][TaskListener=com.activiti.extension.bean.HelloWorldSpringTaskListener@6da401df][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

    10:40:14,693 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldSpringTaskListener  - [Process=142557][event=create][TaskListener=com.activiti.extension.bean.HelloWorldSpringTaskListener@6da401df][ActivityId=userTask1][TaskAssignee=1][TaskForm=null]

     

    So there is a significant difference here to if we use POJO listeners, with Spring bean listeners we will have just one bean instance per listener implementation shared between all process instances.

    Making the Listener implementation a Spring Bean method

    This works in the same way as for service task Spring Bean Methods. See docs.

     

    Having a listener implemented as a Spring bean method looks like this (there is no difference between execution and task listener method implementations, unless you want to pass in an execution or task context):

     

    package com.activiti.extension.bean.service;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.DelegateTask;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;

    @Service
    public class HelloWorldService {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

      public String greeting() {
          return "Hello World from Service!";
      }

      public String customGreetingFromExecListener(
              DelegateExecution execution,
              String text) {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Java Delegate=" + this + "]");

          logger.info("Hello World: " + text);

          return "Something back from service!";
      }

      public String customGreetingFromTaskListener(
              DelegateTask task,
              String text) {
          logger.info("[Process=" + task.getExecution().getProcessInstanceId() +
                      "][Java Delegate=" + this + "]");

          logger.info("Hello World: " + text);

          return "Something back from service!";
      }
    }

     

    And it is used as in the following BPMN 2.0 XML sample:

     

    <process id="TestingListeners" name="Testing Listeners" isExecutable="true">
      <extensionElements>

        <activiti:executionListener
          event="start"
          expression="${helloWorldService.customGreetingFromExecListener(
                      execution, &quot;Hello-start&quot;)}"
    />


        <activiti:executionListener
          event="end"
          expression="${helloWorldService.customGreetingFromExecListener(
                     execution, &quot;Hello-end&quot;)}"
    />


      </extensionElements>

      <startEvent id="startEvent" name="Start Event"></startEvent>

      <userTask id="userTask1" name="User Task 1" activiti:assignee="$INITIATOR">
        <extensionElements>
          <activiti:executionListener event="start"
             delegateExpression="${helloWorldExecutionListener}"/>

          <activiti:executionListener event="end"
             delegateExpression="${helloWorldExecutionListener}"/>


          <activiti:taskListener
            event="create"
            expression="${helloWorldService.customGreetingFromTaskListener(
                       task, &quot;Hello-task-create&quot;)}"
    />

          <activiti:taskListener
            event="assignment"
            expression="${helloWorldService.customGreetingFromTaskListener(
                       task, &quot;Hello-task-assignment&quot;)}"
    />


          <activiti:taskListener event="complete"
             delegateExpression="${helloWorldTaskListener}"/>

          <activiti:taskListener event="delete"
             delegateExpression="${helloWorldTaskListener}"/>

      </extensionElements>
    </userTask>

     

    We can see here that some of the execution listeners and some of the task listeners have been changed to use the new Spring Bean method implementations. Let’s see what that looks like when we run the process.

     

    Starting process one, not completing user task 1:

     

    11:12:47,495 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145020][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:12:47,495 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-start

    11:12:47,498 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=145020][event=take][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@7076b9d][ActivityId=startEvent]

    11:12:47,500 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=145020][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@7076b9d][ActivityId=userTask1]

    11:12:47,502 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145020][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:12:47,502 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-task-assignment

    11:12:47,504 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145020][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:12:47,504 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-task-create

     

    As we can see, same Spring Bean instance are used for all calls. If we start a second process instance and stop before completing User task 1:

     

    11:14:52,185 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145026][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:14:52,186 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-start

    11:14:52,186 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=145026][event=take][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@7076b9d][ActivityId=startEvent]

    11:14:52,187 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldSpringExecutionListener  - [Process=145026][event=start][ExecutionListener=com.activiti.extension.bean.HelloWorldSpringExecutionListener@7076b9d][ActivityId=userTask1]

    11:14:52,189 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145026][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:14:52,189 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-task-assignment

    11:14:52,189 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Process=145026][Java Delegate=com.activiti.extension.bean.service.HelloWorldService@41c60c23]

    11:14:52,189 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Hello-task-create

     

    So here is also a difference from Spring Bean implemented listeners, when we use expressions to call Spring Bean methods we will always just have one Spring Bean instance shared both inside and between process instances. This is okay when using the method approach (or expression approach as it is also referred to) as all context is passed in via parameters, which makes it thread safe.

    Listening to login and logout events

    There are special interfaces you can implement to listen to login and logout operations. For login events implement the LoginListener interface as follows:

     

    package com.activiti.extension.bean;

    import com.activiti.api.security.LoginListener;
    import com.activiti.domain.idm.User;
    import org.springframework.stereotype.Component;

    @Component
    public class LoginListenerImpl implements LoginListener {
      @Override
      public void onLogin(User user) {
          // User <user> logged in, do something

      }
    }

     

    And for logout events implement the LogoutListener as follows:

     

    package com.activiti.extension.bean;

    import com.activiti.api.security.LogoutListener;
    import com.activiti.domain.idm.User;
    import org.springframework.stereotype.Component;

    @Component
    public class LogoutListenerImpl implements LogoutListener {

      @Override
      public void onLogout(User user) {
        // User <user> logged out, do something

      }
    }

     

    Note that these classes need to be Spring beans and they need to be located in the com.activiti.extension.bean package to be picked up as such.

    Listening to any event

    In some cases we might just want to listen to all the events generated by the Activiti workflow engine. And maybe offload them to an external system for further processing. This can be done by implementing the RuntimeEventListener interface as follows:

     

    package com.activiti.extension.bean;

    import com.activiti.service.runtime.events.RuntimeEventListener;
    import org.activiti.engine.delegate.event.ActivitiEvent;
    import org.springframework.stereotype.Component;

    @Component
    public class RuntimeEventListenerImpl implements RuntimeEventListener {

      @Override
      public boolean isEnabled() {
          return true;
      }

      @Override
      public void onEvent(ActivitiEvent activitiEvent) {
          // Handle event here, offload to external system maybe
      }

      @Override
      public boolean isFailOnException() {
          return false;
      }
    }

     

    Note that this class need to be a Spring bean and it need to be located in the com.activiti.extension.bean package to be picked up as such.

    Implementing Script backed Listeners

    In some cases it could be useful to be able to implement a listener quickly without having to restart the server. This can be achieved with Script Listeners.

    Coding the Scripts

    This section goes through how to implement both an execution listener as a script and a task listener as a script.

    Implementing an Execution Listener as a Script

    To set up a script execution listener we use the out-of-the-box execution listener class called org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener. We set two fields for it where the first field is called language, which specifies in what scripting language the script is written in. And the second field is called script and contains the actual code.

     

    The following is an example of how to configure a script based execution listener for the process start event:

     

     

    The resulting BPMN 2.0 XML looks like this:

     

    <extensionElements>
      <activiti:executionListener
        event="start" class="org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener">

          <activiti:field name="script">
              <activiti:string><![CDATA[execution.setVariable('varSetInScript', 'val1');]]></activiti:string>
          </activiti:field>
          <activiti:field name="language">
              <activiti:string><![CDATA[javascript]]></activiti:string>
          </activiti:field>
      </activiti:executionListener>

      <activiti:executionListener
        event="end"
        expression="${helloWorldService.customGreetingFromExecListener(execution, &quot;Hello-end&quot;)}"/>


    </extensionElements>

     

    This script execution listener just sets a process variable called varSetInScript to val1.

    Implementing a Task Listener as a Script

    To set up a script task listener we use the out-of-the-box task listener class called org.activiti.engine.impl.bpmn.listener.ScriptTaskListener. We set two fields for it where the first field is called language, which specifies in what scripting language the script is written in. And the second field is called script and contains the actual code.

     

    The following is an example of how to configure a script based task listener for the task create event:

     

     

    The resulting BPMN 2.0 XML looks like this:

     

    <userTask id="userTask1" name="User Task 1" activiti:assignee="$INITIATOR">
    <extensionElements>
      <activiti:executionListener event="start" delegateExpression="${helloWorldExecutionListener}"/>
      <activiti:executionListener event="end" delegateExpression="${helloWorldExecutionListener}"/>

      <activiti:taskListener event="create" class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener">
          <activiti:field name="script">
              <activiti:string><![CDATA[task.name = 'User Task ' + task.getExecution().getVariable('varSetInScript');]]></activiti:string>
          </activiti:field>
          <activiti:field name="language">
              <activiti:string><![CDATA[javascript]]></activiti:string>
          </activiti:field>
      </activiti:taskListener>

      <activiti:taskListener event="assignment" expression="${helloWorldService.customGreetingFromTaskListener(task, &quot;Hello-task-assignment&quot;)}"/>
      <activiti:taskListener event="complete" delegateExpression="${helloWorldTaskListener}"/>
      <activiti:taskListener event="delete" delegateExpression="${helloWorldTaskListener}"/>
    </extensionElements>

     

    The script task listener just takes the varSetInScript variable value and then uses it as part of the task name.

     

    Running it should give a User Task 1 with a name as follows:

     

    Should we use Script or Java based Listeners?

    The following list some of the pros and cons with Java backed and Script backed listeners.

     

    Java

    Pros: Easy to debug, easy to test, most likely faster, easier to reuse code, code completion, secure, you can change them for an active process instance

    Cons: More involved to get going, you need an extension project. Need to stop the server to install a new version, unless using something like JRebel

     

    Scripting

    Pros: Very easy to get going with it and you don’t have to leave the BPMN Editor, no other tools required, no server restarts required

    Cons: Changes to script cannot be used by active process instances, can be a security risk (however, just recently there have been the release of so called secure script listeners that can be used to handle certain security risks), harder to debug problems, more error prone as you see error first during runtime, not easy to reuse code, not easy to create tests

    Table of Contents                   

     

    Introduction

    Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

     

    1. As a POJO, which is called a Java Delegate
    2. As a Spring Bean Java Delegate
    3. As a Spring Bean method (what this article covers)

     

    What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

     

    There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

     

    1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
    2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
    3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

     

    What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean Java Delegate approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

     

    Or you can use the Spring Bean Method approach, which this article covers, and which solves a lot of concurrency issues.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

    Implementing a Hello World Spring Bean Method

    Let’s start the usual way with a Hello World Spring Bean Method. This differs from the other approaches as we are not implementing one specific execute method for a Java Delegate. Here we are implementing as many methods as we want, serving as many service tasks as we need from the same Spring Bean implementation.

    Coding the Spring Bean class

    Here is the implementation of the class:

     

    package com.activiti.extension.bean.service;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;


    @Service
    public class HelloWorldService {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

      public String greeting() {
          return "Hello World from Service!";
      }

      public void customGreeting(String text) {
          logger.info("[Java object=" + this + "]");
          logger.info("Hello World: " + text);
      }
    }

     

    So every method we provide inside the Spring Bean class implementation is a potential Service Task implementation. In this case we are going to use the customGreeting method as the implementation and pass in different texts depending on what service task that is calling the method.

     

    This is completely thread safe as we are not using any class members, just passing in data via a method parameter. However, the logging library needs to be thread safe as this method can be called from multiple concurrent threads, serving different service tasks.

    Testing the method

    Now to test the Spring Bean method as a service task implementation create a process model looking like this:

     

     

    And the Service Task is connected to the Spring Bean method implementation via the Expression property:

     

     

    The first service task has the following Expression:

     

    ${helloWorldService.customGreeting('Service Task 1')}

     

    And the second task has the following Expression:

     

    ${helloWorldService.customGreeting('Service Task 2')}

     

    In BPMN 2.0 XML it will look like this:

     

    <serviceTask id="serviceTask1"
                 name="Service Task 1 (Spring Bean method customGreeting(x))"
                 activiti:expression="${helloWorldService.customGreeting('Service Task 1')}">

     

    When we run this process it will print similar logs to the following:

     

    03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

    03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 1

    03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

    03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 2

     

    We can see here that all the context for the method is passed in as a parameter. In this case it’s only a String of text, but could be any parameters you like. So using the same Spring bean works fine as the logging library is thread safe.

     

    Now, what about process information and process variables? How do I get to them? That’s easy, just add an extra parameter of type DelegateExecution:

     

    package com.activiti.extension.bean.service;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;

    @Service
    public class HelloWorldService {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

      public String greeting() {
          return "Hello World from Service!";
      }

      public void customGreeting(DelegateExecution execution, String text) {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Java Delegate=" + this + "]");

          logger.info("Hello World: " + text);
      }
    }

     

    And update the Expression as follows:

     

    ${helloWorldService.customGreeting(execution, 'Service Task 1')}

    Using Process Variables in the Spring Bean Method

    In a Spring Bean Method we access process variables in the same way as in a POJO Java Delegate, so read its docs.

    Returning data from the Spring Bean Method

    There are probably many situations where we would want to return some data from the Spring Bean method and use it in the process. We can achieve this by defining return values for the service task. First update the customGreeting method to return a String:

     

    public String customGreeting(DelegateExecution execution, String text) {
      logger.info("[Process=" + execution.getProcessInstanceId() + "][Java Delegate=" + this + "]");
      logger.info("Hello World: " + text);

      return "Something back from service!";
    }

     

    Now, to return this back to the process instance as a process variable we need to do the following property configuration for each service task:

     

     

    Basically we need to specify the process variable name as the Result variable name property.

     

    In BPMN 2.0 XML it will look like this:

     

    <serviceTask id="serviceTask1"
                 name="Service Task 1 (Spring Bean method customGreeting(x))"
                 activiti:expression="${helloWorldService.customGreeting(execution, 'Service Task 1')}"
                 activiti:resultVariableName="greetingFromService">

     

    You could now use the REST API to list the variables available for the process instance after the Service Tasks have been executed, we should see the greetingFromService variable:

     

    The URL path parameter 122501 in the above example is the process instance ID.

    Using Field Injection in the Spring Bean Method

    Not available when using Spring Bean Methods.

    Injecting Beans into the backing Spring Bean

    When we use Spring we most likely want to use other Spring Beans in our Java Delegate implementation. This can be Spring beans that we create and out-of-the-box Spring beans.

     

    This works the same way as for Spring Bean Java Delegates, see docs.

    Calling Spring Bean Methods Asynchronously

    See the POJO Java Delegate docs.

    Table of Contents                   

     

    Introduction

    Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

     

    1. As a POJO, which is called a Java Delegate
    2. As a Spring Bean Java Delegate (what this article covers)
    3. As a Spring Bean method

     

    What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

     

    There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

     

    1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
    2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
    3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

     

    What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

     

    This article cover the second approach - Spring Bean Java Delegate.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

    Implementing a Hello World Spring Bean Java Delegate

    This is pretty much the same thing as implementing a POJO Java Delegate, you just “Springify” it a bit so it can act as a Spring Bean and wire in other Spring Beans. Let’s start the usual way with a Hello World Spring Bean Java Delegate. However, we are going to take the opportunity to check process IDs and object instance IDs while we are at it, so the log message from the Spring Bean Java Delegate will be a little bit different than the usual “Hello World”.

    Coding the Java class

    Here is the implementation of the Spring Bean Java Delegate class:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.JavaDelegate;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    @Component("helloWorld")
    public class HelloWorldSpringJavaDelegate implements JavaDelegate {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

      @Override
      public void execute(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Spring Java Delegate=" + this + "]");
      }
    }

     

    There are two things here that differentiate a Spring Bean Java Delegate implementation from a POJO Java Delegate implementation. The first is that it is annotated with org.springframework.stereotype.Component, setting it up to be discovered and  registered as a Bean in the Spring application context. The second thing, which is very important, is that the class needs to be defined in the com.activiti.extension.bean package, or any sub-package, for the Spring component scanning to find it.

     

    Note here that the Spring Bean will have the name helloWorld, as specified via the @Component annotation. If we don’t specify a name then the classname will be used with first letter in lower-case.

     

    The rest of the implementation is the same as for the POJO Java Delegate, so read through the implementation section for it.

    Testing the Spring Bean Java Delegate

    Now to test the Java Delegate implementation create a process model looking like this:

     

     

    And the Service Task is connected to the Spring Bean Java Delegate implementation via the Delegate expression property:

     

     

    In BPMN 2.0 XML it will look like this:

     

    <serviceTask id="sid-7C83EB30-8E02-400B-BEEF-CAE34BFB6FFD"
                 name="Service Task 1 (Spring Bean Java Delegate)"
                 activiti:delegateExpression="${helloWorld}">

     

    Note here that instead of using activiti:class we use activiti:delegateExpression to tell Activiti about the Spring Bean name. An activiti:delegateExpression can be used when the expression resolves to a Java object instance that implements the JavaDelegate interface.

     

    When we run this process it will print similar logs to the following:

     

    02:43:54,236 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102534][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

    02:43:54,238 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102534][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

     

    We can immediately see the difference from when using POJO Java Delegates, there is only one instance of the Spring Bean used by both service tasks. This is the standard way the Spring container works, it creates Singletons. If we let the process instance stay at the User Task and start another process, then we will see logs such as follows for the second process instance:

     

    02:46:20,065 [http-nio-8080-exec-2] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102542][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

    02:46:20,070 [http-nio-8080-exec-2] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102542][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

     

    And we can see that the initial Spring Bean instance is used also by the second process instance. So we need to make sure classes that we use are thread-safe. And that access to class members are serialized.

    Spring Bean Java Delegates and Thread Safety

    When using the activiti:delegateExpression attribute, the thread-safety of the delegate instance will depend on how the expression is resolved. If the delegate expression is reused in various tasks and/or process definitions, and the expression always returns the same instance, then it is not thread-safe. Let’s look at a few examples to clarify.

     

    Suppose the expression is ${helloWorld}, like in our example above, which resolves to a shared single Spring Bean JavaDelegate instance. Then using this expression in different tasks and/or process definitions, and having it always resolve to the same instance, will cause problems unless we are careful. In this case, thread safe libraries must be used, and member variables cannot be used unless access is synchronized.

     

    However, if the expression looks something like this ${helloWorldFactory.createDelegate(someVariable)}, where helloWorldFactory is a Spring Bean factory that creates a new instance each time the expression is resolved. Then there is no problem with regards to thread-safety. Same thing if we re-implement the Java Delegate class with Spring bean scope PROTOTYPE, more on this further on in this article.

    Using Process Variables in the Spring Bean Java Delegate

    In a Spring Bean Java Delegate we access process variables in the same way as in a POJO Java Delegate, so read its docs.

    Using Field Injection in the Spring Bean Java Delegate

    Now, let’s say you wanted to use the Spring Bean Java Delegate from multiple service tasks in your process but have the implementation behave a little bit different depending on from which service task it is invoked. Basically you want configure the implementation for each service task.

     

    This can be done easily using Class fields and field injection. Let’s set a separate greeting for each service task. This can be done as follows, first click on Class fields for the service task:

     

     

    Then set them up, for Service Task 1:

     

     

    And for Service Task 2:

     

     

    The BPMN 2.0 looks like this:

     

    <serviceTask id="sid-7C83EB30-8E02-400B-BEEF-CAE34BFB6FFD" 
                 name="Service Task 1 (Spring Bean Java Delegate)"
                 activiti:delegateExpression="${helloWorld}">

      <extensionElements>
          <activiti:field name="greeting">
              <activiti:string><![CDATA[Hello from Service Task 1]]></activiti:string>
          </activiti:field>
      </extensionElements>
    </serviceTask>

     

    We can now access this field in the Java Delegate implementation via an org.activiti.engine.delegate.Expression:

     

    @Component("helloWorld")
    public class HelloWorldSpringJavaDelegate implements JavaDelegate {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

      private Expression greeting;

      public void setGreeting(Expression greeting) {
          this.greeting = greeting;
      }

      @Override
      public void execute(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Spring Java Delegate=" + this + "]");

          String greetingText = (String) greeting.getValue(execution);
          logger.info("The greeting set for this service task is: " + greetingText);
      }
    }

     

     

    The field value is injected through a public setter method on your Java Delegate class, following the Java Bean naming conventions (e.g. field <activiti:field name="greeting"> has setter setGreeting(…)).

     

    The output from this implementation looks like this:

     

    09:58:57,566 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=107501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@f787778]

    09:58:57,566 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

     

    09:58:57,567 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=107501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@f787778]

    09:58:57,567 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

     

    So when we used the POJO Java Delegate we could understand why this would work as there is one class instance per service task in a process definition. How come it works for Spring Bean Java Delegates too, when only a single instance of the bean is used by all service tasks?

     

    A class field is stateless and thus can be shared between all service tasks and process instances. The secret is in the greeting.getValue(execution) call. Multiple calls to <Expression>.getValue(<DelegateExecution>) can happen concurrently without problems, Activiti will figure out what value to return depending on current process instance.

    Field Injection and Thread Safety

    Field Injection is not thread-safe when using singleton Spring Bean Java Delegates. You could have multiple parallel execution paths in a process instance, and multiple process instances running in parallel, calling the same Java Delegate instance. And as there is only one instance of the delegate and X number of threads trying to inject an org.activiti.engine.delegate.Expression, then this will lead to race conditions.

     

    To solve this problem we could rewrite the Spring Bean Java delegate to use an expression and passing the needed data to the delegate via method arguments. Or we could change our example to create a bean per service task instance instead, using the PROTOTYPE scope:

     

    @Component("helloWorld")
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public class HelloWorldSpringJavaDelegate implements JavaDelegate {

     

    The the logs would then look like this:

     

    12:41:18,400 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@fe8507a]

    12:41:18,400 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

    12:41:18,401 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@6f6558f4]

    12:41:18,401 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

     

    So this looks better, one instance per service task. If we start another process instance it will look like this:

     

    12:46:20,078 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112509][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1168625f]

    12:46:20,078 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

    12:46:20,079 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112509][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@641c0070]

    12:46:20,079 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

     

    As we can see, we got four new instances created for our delegate, making it all thread safe. This might not be the most efficient solution if the production environment will have thousands of process instances running in parallel. See the Spring Bean Method approach for another solution.

     

    Note. As of Activiti version 5.21, the process engine configuration can be configured in a way to disable the use of field injection on delegate expressions, by setting the value of the delegateExpressionFieldInjectionMode property (which takes one of the values in the org.activiti.engine.imp.cfg.DelegateExpressionFieldInjectionMode enum).

     

    Following settings are possible:

     

    • DISABLED: fully disables field injection when using delegate expressions. No field injection will be attempted. This is the safest mode, when it comes to thread-safety.
    • COMPATIBILITY: in this mode, the behaviour will be exactly as it was before version 5.21: field injection is possible when using delegate expressions and an exception will be thrown when the fields are not defined on the delegate class. This is of course the least safe mode with regards to thread-safety, but it can be needed for backwards compatibility or can be used safely when the delegate expression is used only on one task in a set of process definitions (and thus no concurrent race conditions can happen).
    • MIXED: Allows injection when using delegateExpressions but will not throw an exception when the fields are not defined on the delegate. This allows for mixed behaviours where some delegates have injection (for example because they are not singletons) and some don’t.

     

    The default mode for Activiti version 5.x is COMPATIBILITY.

    Injecting Beans into Spring Bean Java Delegates

    When we use Spring we most likely want to use other Spring Beans in our Java Delegate implementation. This can be Spring beans that we create and out-of-the-box Spring beans.

     

    Let’s create a super simple bean that we can call from our delegate:

     

    package com.activiti.extension.bean.service;

    import org.springframework.stereotype.Service;

    @Service
    public class HelloWorldService {
      public String greeting() {
          return "Hello World from Service!";
      }
    }

     

    Note that it is located in the com.activiti.extension.bean package, this means it will be scanned by Activiti and registered in the Spring application context. Now when this is done we can update our Spring Bean Java Delegate to wire in this bean plus an out-of-the-box bean called UserService:

     

    package com.activiti.extension.bean;

    import com.activiti.domain.idm.User;
    import com.activiti.extension.bean.service.HelloWorldService;
    import com.activiti.service.api.UserService;
    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.Expression;
    import org.activiti.engine.delegate.JavaDelegate;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;


    @Component("helloWorld")
    public class HelloWorldSpringJavaDelegate implements JavaDelegate {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

      /**
       * Expression representing the greeting class field
       */

      private Expression greeting;

      @Autowired
      HelloWorldService simpleService;

      @Autowired
      UserService userService;

      public void setGreeting(Expression greeting) {
          this.greeting = greeting;
      }

      @Override
      public void execute(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Spring Java Delegate=" + this + "]");

          String greetingText = (String) greeting.getValue(execution);
          logger.info("The greeting set for this service task is: " + greetingText);

          logger.info("Injected Spring Bean greeting: " + simpleService.greeting());

          User user = userService.findUser(Long.parseLong((String)execution.getVariable("initiator")));
          String username = user.getFirstName() + " " + user.getLastName();
          logger.info("Initiator is: " + username);
      }
    }

     

    Running this produces a log similar to this:

     

    02:03:21,181 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=115001][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1251052b]

    02:03:21,181 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

    02:03:21,182 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Injected Spring Bean greeting: Hello World from Service!

    02:03:21,184 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Initiator is: null Administrator

    02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=115001][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1251052b]

    02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

    02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Injected Spring Bean greeting: Hello World from Service!

    02:03:21,186 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Initiator is: null Administrator

     

    Accessing the HelloWorldService works nicely, mostly because it just returns a static String literal. But if you are calling methods that manipulate data you have to make them thread safe. Using the out-of-the-box UserService was also no problem, note that the Admin user does not have a first name and hence null.

     

    Note. the order in which beans are read does not matter, the scanner will collect all bean info first and then create them in necessary order, just like it would work with XML based configuration of beans. The important thing is that all beans are in packages that are scanned.

    Calling Spring Bean Java Delegates Asynchronously

    See the POJO Java Delegate docs.

    Table of Contents                   

     

    Introduction

    Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

     

    1. As a POJO, which is called a Java Delegate (what this article covers)
    2. As a Spring Bean Java Delegate
    3. As a Spring Bean method

     

    What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

     

    There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

     

    1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
    2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
    3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

     

    What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

     

    This article cover the first approach - POJO Java Delegate.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

    Implementing a Hello World Java Delegate

    Let’s start the usual way with a Hello World Java Delegate. However, we are going to take the opportunity to check process IDs and object instance IDs while we are at it, so the log message from the Java Delegate will be a little bit different than the usual “Hello World”.

    Coding the Java class

    Here is the implementation of the Java Delegate class:

     

    package com.activiti.extension.bean;

    import org.activiti.engine.delegate.DelegateExecution;
    import org.activiti.engine.delegate.JavaDelegate;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    public class HelloWorldJavaDelegate implements JavaDelegate {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldJavaDelegate.class);

      @Override
      public void execute(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Java Delegate=" + this + "]");
      }
    }

     

    The class has been created in the com.activiti.extension.bean package, but it does not have to be located in this package. It is just good practice to put the class in this package if it ever needs to be converted to a Spring Java Delegate in the future. Then this package is scanned automatically by Activiti and any beans are instantiated.

     

    A Java Delegate needs to implement the org.activiti.engine.delegate.JavaDelegate interface, which contains the execute method. This methods takes one parameter of type org.activiti.engine.delegate.DelegateExecution, which is how we get access to the process instance that is invoking this Java Delegate. As said in the introduction, a Java Delegate object instance for a Service Task is shared between process instances, so the DelegateExecution parameter is our way of finding out information about the active process instance.

     

    Notice how we use an external class here for logging and that the logger.info method could be called concurrently by multiple threads. So it needs to be thread safe, which it is. Same thing applies for other class members that we define and use in the execute method. We can do whatever we want inside the execute method as all threads have independent call stacks.

    Testing the Java Delegate

    Now to test the Java Delegate implementation create a process model looking like this:

     

    Both of these Service Tasks uses the Java Delegate that we implemented:

     

    In BPMN 2.0 XML it will look like this:

     

    <serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE"
      name="Service Task 1 (Java Delegate D1)"
      activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

     

    When we run this process it will print similar logs to the following:

     

    11:17:31,894 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70005][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@7ce61257]

    11:17:31,896 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70005][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@2394529d]

     

    If we let the process instance stay at the User Task and start another process, then we will see logs such as follows for the second process instance:

     

    11:20:07,035 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70015][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@7ce61257]

    11:20:07,036 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70015][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@2394529d]

     

    What we can see here is that when inside one process instance each Service Task will have its unique version of the Java Delegate object instance (i.e. 7ce61257 and 2394529d). However, when starting a second process instance, it will reuse the Java Delegate instances from the first process instance.

    Java Delegates and Thread Safety

    When using the activiti:class attribute, things are not thread safe even if each service task has its own Java Delegate instance. This is because Java Delegate instances are shared between process instances. And this could potentially lead to library code used in the Java Delegate implementation being called concurrently, and as a result it needs to be thread-safe, as the log library we used in our example. Same thing for any class member variables, access needs to be synchronized.

     

    So use thread-safe libraries and avoid class members.

    Using Process Variables in the Java Delegate

    Most of the time when you implement a Java Delegate you would want to use some process variable values. Process variables are typically set when the process instance is started, through the API, or by different activities in the process. They are stored in the database for each process instance.

     

    So how do you pass these process variable values into the Java Delegate? It is not necessary to pass any value, you can access all process instance variables via the DelegateExecution object and the getVariable method.

     

    There is one process variable that you can access immediately to try this out, and that is the initiator:

     

    String initiator = (String)execution.getVariable("initiator");

    logger.info("Initiator of the process has user ID = " + initiator);

     

    To print all process variables for the process instance you can do this:

     

    logger.info("--- Process variables:");
    Map<String, Object> procVars = execution.getVariables();
    for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
      logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
    }

     

    It is equally easy to create a new process variable via the setVariable method on the DelegateExecution object:

     

    execution.setVariable("greeting", "Hello World!");

     

    When you create new variables they are created in a scope, similar to how you create variables in a Java program. For example, if you create a variable inside a Java class method it is scoped to that method and not accessible outside of it. Variables in the Activiti workflow engine are scoped at for example process instance level, sub-process instance level, user task instance level etc. If you are creating a variable that already exist in the scope, then it will be overwritten with the new value.

     

    However, there is a difference between Java and Activiti, when we create a variable with the setVariable method it will traverse the scope hierarchy until it reaches the top root scope and set the variable there, unless there is a local variable with the same name in some scope on the way to the root scope, in that case this local variable is overwritten.

     

    So when we created the variable greeting it was set on the root process instance scope. This is because service tasks, in contrast to user tasks, have the same scope as the process instance they are created in.

     

    If you want to be sure to set a variable at the scope you are at, then you should use the setVariableLocal method instead:

     

    execution.setVariableLocal("greetingLocal", "Hello World Local!");

     

    This has no effect on a service task as it does not have its own scope like a user task, unless it’s in an external process invoked via <callActivity id="someProcessId".

     

    Let’s say we now have the following implementation:

     

    public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Java Delegate=" + this + "]");
      logger.info("[ActivityName=" + execution.getCurrentActivityName() +
                  "][ActivityId=" + execution.getCurrentActivityId() + "]");

      String initiator = (String)execution.getVariable("initiator");
      logger.info("Initiator of the process has user ID = " + initiator);

      execution.setVariable("greeting", "Hello World!");
      execution.setVariableLocal("greetingLocal", "Hello World Local!");

      logger.info("--- Process variables:");
      Map<String, Object> procVars = execution.getVariables();
      for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
          logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
      }
    }

     

    This will print a log looking something like this:

     

    08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=97501][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@132faf11]

    08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

    08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

    08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

    08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

    08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

    08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

     

    Important. as this is not a user task you cannot use the TaskService and get a task instance, such as:

     

    TaskService taskService = execution.getEngineServices().getTaskService();
    Task task = taskService.createTaskQuery().singleResult();
    String taskId = task.getId();

     

    You will get a NullPointerException on line 3.

     

    Now, what happens if we move one of the Service tasks into a sub-process:

     

     

    This now produces the following log:

     

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=100011][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@44749768]

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

    09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

    09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=100011][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@5183bd3f]

    09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 2 (Java Delegate D1)][ActivityId=sid-69D06583-0BF7-43A6-8C28-2A65EEF1508C]

    09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

    09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

    09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

    09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

    09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

     

    If you have a normal sub-process like in our example above with <subProcess id="subProcess1", it is modelled as a part of the parent process (in the same BPMN and as a child of the XML element).

     

    If the sub-process you want to call is subject to change, independent of the parent-process, you can use a <callActivity id="someProcessId". This sub-process is called an external process (not part of the parent BPMN, but lives in it's own BPMN). This allows you to redeploy the child-process without the need to alter the parent-process.

     

    A normal sub-process is treated as a direct child of the process-instance, so you can access the parent's process variables from within the sub-process, as you have seen above. On the contrary, a call-activity does not have access to the parent variables and is a "process instance" itself. You can expose variables from the parent-process to the call-activity by using the in/out declarations.

    How many variables are really fetched with getVariable?

    This might sound like a weird question, one right? No, behind the scenes all variables are fetched and cached when you call the getVariable method for the first time. This might not always be ideal, if you for example have loads of variables. To just fetch the specified variable you need to use another method signature that takes an extra parameter:

     

    String initiator = (String)execution.getVariable("initiator", false);

     

    In this case the false parameter says that we do not want to fetch all variables. Just the one we actually specified, initiator in this case.

    Using Field Injection in the Java Delegate

    Now, let’s say you wanted to use the Java Delegate from multiple service tasks in your process but have the implementation behave a little bit different depending on from which service task it is invoked. Basically you want configure the implementation for each service task.

     

    This can be done easily using Class fields and field injection. Let’s set a separate greeting for each service task. This can be done as follows, first click on Class fields for the service task:

     

     

    Then set them up, for Service Task 1:

     

     

    And for Service Task 2:

     

     

    The BPMN 2.0 looks like this:

     

    <serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE" 
                 name="Service Task 1 (Java Delegate D1)"
                 activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

      <extensionElements>
          <activiti:field name="greeting">
              <activiti:string><![CDATA[Hello from Service Task 1]]></activiti:string>
          </activiti:field>
      </extensionElements>
    </serviceTask>

     

    We can now access this field in the Java Delegate implementation via an org.activiti.engine.delegate.Expression:

     

    public class HelloWorldJavaDelegate implements JavaDelegate {
      private static Logger logger = LoggerFactory.getLogger(HelloWorldJavaDelegate.class);

      private Expression greeting;

      public void setGreeting(Expression greeting) {
         this.greeting = greeting;
      }

      @Override
      public void execute(DelegateExecution execution) throws Exception {
          logger.info("[Process=" + execution.getProcessInstanceId() +
                      "][Java Delegate=" + this + "]");
          logger.info("[ActivityName=" + execution.getCurrentActivityName() +
                      "][ActivityId=" + execution.getCurrentActivityId() + "]");

          String greetingText = (String) greeting.getValue(execution);
          logger.info("The greeting set for this service task is: " + greetingText);
      }
    }

     

    The field value is injected through a public setter method on your Java Delegate class, following the Java Bean naming conventions (e.g. field <activiti:field name="greeting"> has setter setGreeting(…)).

     

    The output from this implementation looks like this:

     

    10:46:50,956 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102511][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@6618bfad]

    10:46:50,956 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

    10:46:50,957 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

     

    10:46:50,958 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102511][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@588a2f17]

    10:46:50,958 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 2 (Java Delegate D1)][ActivityId=sid-69D06583-0BF7-43A6-8C28-2A65EEF1508C]

    10:46:50,959 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

     

    So as you might have figured out, using class fields are thread safe as they are set on the class when it is first initiated. And there is one class instance per service task. As usual, Java Delegate object instances are shared between process instances.

    Field Injection and Thread Safety

    When using the activiti:class attribute, using field injection is always thread safe. For each service task that references a certain class, a new Java Delegate instance will be instantiated and fields (org.activiti.engine.delegate.Expression) will be injected once when the instance is created. Reusing the same class in multiple service tasks and process definitions is no problem.

    Calling Java Delegates Asynchronously

    So far the service tasks have been defined in the process definition in the default way, which means that they will be called synchronously in the same transaction and thread as the transaction and thread the process was started in.

     

    So when the process instance execution reaches Service Task 1, it will have to stop and wait for the Java Delegate implementation to complete, and then the same for Service Task 2. When a service task contains long-running logic, like the invocation of an external Web Service or the construction of a large PDF document, this may not be the desired behaviour.

     

    Activiti provides a solution for these cases in the form of async continuations. From a BPMN 2.0 XML perspective, the definition of an asynchronous service task (or another type of task) is easy. You only have to add an async attribute to the service task configuration:

     

    <serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE"
                 name="Service Task 1 (Java Delegate D1)"
                 activiti:async="true"
                 activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

     

    When we define a service task with the async attribute set to true, the execution of the

    service task logic will be executed in a separate transaction and thread.

    Table of Contents          

             

    Introduction

    Most customizations developed for SkyVault Activiti will require an extension project that produces a JAR that can be added to the application server’s classpath. These are for example Java customizations such as service tasks, task and execution listeners, custom REST APIs etc.

     

    All the artifacts (i.e. JAR files) that make up the Activiti Enterprise server and its extension points are available in SkyVault’s Nexus repo accessible at https://artifacts.alfresco.com. To bring them into the extension project Apache Maven’s dependency management is used.

     

    The project structure that is used in the Activiti Extension project is based on maven’s standard directory layout. So, as you might have figured out, the Activiti Extension project is based completely on Maven and Java.

    Source Code

    Source code for the Activiti Developer Series can be found here.

    Prerequisites

    This section covers the tools needed to work with the Activiti Extension project.

    Java Development Kit (JDK)

    This section steps you through installing the JDK and verifying its installation. There are no prerequisites for this installation. To use the Activiti Extension project most effectively, and to align with what JDK is used by the Activiti Enterprise versions, you need to have Oracle JDK 1.8 installed. Note that Maven requires that the JDK be installed - the Java run-time alone is not sufficient.

     

    Checking for the availability of the JDK:

     

       

      Your MAVEN_OPTS and M2_HOME environment variables are now set. Feel free to increase the specified memory settings if required, for example, if you get "out of memory" errors when building your projects.

      Creating the Activiti Extension Project

      We are now ready to create the actual Activiti Extension project. Create a directory to hold your project, for example activiti-extension-jar. Then create a Maven pom.xml file in this directory with the following content:

       

      <?xml version="1.0" encoding="UTF-8"?>

      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

        <modelVersion>4.0.0</modelVersion>

        <groupId>com.activiti.extension</groupId>

        <artifactId>activiti-extension-jar</artifactId>

        <version>1.0-SNAPSHOT</version>

        <name>Activiti Extension Module</name>

        <description>Activiti Extension Module that produces a JAR file with Java extensions such as service task delegates.</description>

        <packaging>jar</packaging>

       

        <properties>

            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

       

            <!-- Properties used in dependency declarations -->

            <activiti.groupId>com.activiti</activiti.groupId>

            <activiti.version>1.5.1</activiti.version>

            <activiti.engine.version>5.21.0.2</activiti.engine.version>

       

            <!-- Compile with Java 8, default is 5 -->

            <maven.compiler.source>1.8</maven.compiler.source>

            <maven.compiler.target>1.8</maven.compiler.target>

        </properties>

       

        <dependencies>

            <!-- The main Activiti Enterprise application dependency that brings in all

                 needed classes to compile your customizations -->

            <dependency>

                <groupId>${activiti.groupId}</groupId>

                <artifactId>activiti-app-logic</artifactId>

                <version>${activiti.version}</version>

            </dependency>

       

            <!-- Testing -->

            <dependency>

                <groupId>junit</groupId>

                <artifactId>junit</artifactId>

                <version>4.11</version>

                <scope>test</scope>

            </dependency>

        </dependencies>

       

        <build>

            <resources>

                <!-- Filter the resource files in this project and

      do property substitutions -->

                <resource>

                    <directory>src/main/resources</directory>

                    <filtering>true</filtering>

                </resource>

            </resources>

            <testResources>

                <!-- Filter the test resource files in this project and

      do property substitutions -->

                <testResource>

                    <directory>src/test/resources</directory>

                    <filtering>true</filtering>

                </testResource>

            </testResources>

        </build>

       

        <!--

            SkyVault Maven Repositories

            -->

        <repositories>

            <!-- Activiti Enterprise Edition Artifacts,

      put username/pwd for server in settings.xml -->

            <repository>

                <id>activiti-private-repository</id>

      <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-enterprise-releases</url>

            </repository>

        </repositories>

      </project>

       

      Then create the following project directory structure:

       

      src/activiti-extension-jar$ tree

      .

      ├── pom.xml

      └── src

         ├── main

         │   ├── java

         │   │   └── com

         │   │       └── activiti

         │   │           └── extension

         │   │               ├── bean

         │   │               └── conf

         │   └── resources

         └── test

             └── java

                 └── com

                     └── activiti

                         └── extension

                             └── bean

       

      The different packages that we use for Java classes are important as only some packages are scanned by Activiti, such as the com.activiti.extension.bean package, which is scanned for Spring beans implementing things like service tasks and task listeners. And the com.activiti.extension.conf package, which is scanned for new Spring context configurations. So it is good practice to use these packages as a starting point.

      Building the Activiti Extension Project

      We now have an Activiti extension project based on Apache Maven. To build it we would execute the following command:

       

      ~/src/activiti-extension-jar$ mvn clean install

      [INFO] Scanning for projects...

      [INFO]                                                                         

      [INFO] ------------------------------------------------------------------------

      [INFO] Building Activiti Extension Module 1.0-SNAPSHOT

      [INFO] ------------------------------------------------------------------------

      Downloading:

      ...

      [INFO]

      [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ activiti-extension-jar ---

      [INFO]

      [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ activiti-extension-jar ---

      [INFO] Using 'UTF-8' encoding to copy filtered resources.

      [INFO] Copying 0 resource

      [INFO]

      [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ activiti-extension-jar ---

      [INFO] Nothing to compile - all classes are up to date

      [INFO]

      [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ activiti-extension-jar ---

      [INFO] Using 'UTF-8' encoding to copy filtered resources.

      [INFO] skip non existing resourceDirectory /home/martin/src/activiti-extension-jar/src/test/resources

      [INFO]

      [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ activiti-extension-jar ---

      [INFO] Nothing to compile - all classes are up to date

      [INFO]

      [INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ activiti-extension-jar ---

      [INFO] No tests to run.

      [INFO]

      [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ activiti-extension-jar ---

      [INFO] Building jar: /home/martin/src/activiti-extension-jar/target/activiti-extension-jar-1.0-SNAPSHOT.jar

      [INFO]

      [INFO] --- maven-install-plugin:2.4:install (default-install) @ activiti-extension-jar ---

      [INFO] Installing /home/martin/src/activiti-extension-jar/target/activiti-extension-jar-1.0-SNAPSHOT.jar to /home/martin/.m2/repository/com/activiti/extension/activiti-extension-jar/1.0-SNAPSHOT/activiti-extension-jar-1.0-SNAPSHOT.jar

      [INFO] Installing /home/martin/src/activiti-extension-jar/pom.xml to /home/martin/.m2/repository/com/activiti/extension/activiti-extension-jar/1.0-SNAPSHOT/activiti-extension-jar-1.0-SNAPSHOT.pom

      [INFO] ------------------------------------------------------------------------

      [INFO] BUILD SUCCESS

      [INFO] ------------------------------------------------------------------------

      [INFO] Total time: 3.159 s

      [INFO] Finished at: 2016-10-27T09:24:40+01:00

      [INFO] Final Memory: 23M/381M

      [INFO] ------------------------------------------------------------------------

       

      This builds the project, creates the JAR, and installs the JAR into the local Maven repo at the following location:

       

      martin@gravitonian:~/.m2/repository/com/activiti/extension/activiti-extension-jar/1.0-SNAPSHOT$ ls -l

      total 16

      -rw-rw-r-- 1 martin martin 2291 Oct 27 09:24 activiti-extension-jar-1.0-SNAPSHOT.jar

      -rw-rw-r-- 1 martin martin 2718 Oct 27 09:21 activiti-extension-jar-1.0-SNAPSHOT.pom

      -rw-rw-r-- 1 martin martin  724 Oct 27 09:24 maven-metadata-local.xml

      -rw-rw-r-- 1 martin martin  217 Oct 27 09:24 _remote.repositories

       

      The JAR is also available in the project’s target directory:

       

      martin@gravitonian:~/src/activiti-extension-jar/target$ ls -l

      total 16

      -rw-rw-r-- 1 martin martin 2291 Oct 27 09:24 activiti-extension-jar-1.0-SNAPSHOT.jar

       

      You can copy the JAR from any of these locations into the classpath of your Activiti Enterprise server installation.

      Installing the Activiti Extension JAR

      Now that we got an Activiti Extension JAR, how do we get the Activiti Enterprise server to know about it. We put it on the server’s classpath. Let’s say that our Activiti Enterprise server is installed into the /opt/activiti15 directory. Then we need to put the extension JAR into the /opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib (important: it does not work to put the JAR into tomcat/lib):

       

      martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/src/activiti-extension-jar/target/activiti-extension-jar-1.0-SNAPSHOT.jar .

       

      After we have copied the file into the classpath we need to restart the Activiti server so it has a chance to read the new JAR:

       

      martin@gravitonian:/opt/activiti15$ /opt/activiti15/tomcat/bin/catalina.sh stop

      martin@gravitonian:/opt/activiti15$ /opt/activiti15/h2/stop-h2.sh

      martin@gravitonian:/opt/activiti15$ ./start-activiti.sh

       

      The server is now aware of the new classes in the extension JAR and we can start using them in our process definitions.

      Table of Contents

       

      Introduction

      Apache Camel is a powerful integration framework that allows you to implement the Enterprise Integration Patterns (EAI). Camel provides all the plumbing for you so you can focus on the business logic of the integration.

       

      In SkyVault Activiti (i.e. the Enterprise edition of Activiti) there is a Camel service task available in the BPMN Editor that will get you going with using Camel from an Activiti process. There is actually two different ways we can use Camel with Activiti. We can call Activiti from Camel, and we can call Camel from Activiti. We will look at both cases in this article by using the Camel task to both send messages and describe how you can start an Activiti process from a Camel route.

       

      What you need to know though is that in Activiti Enterprise 1.5.0 the Camel support is not included like it is in the open source Activiti releases. What I mean by this is that there are no Camel related libraries available in the tomcat/webapps/activiti-app/WEB-INF/lib directory. The only Camel support is the Camel task in the BPMN Editor.

       

      Prerequisites

      This section takes you through how to set up an environment to be used when testing Activiti Enterprise with Apache Camel.

       

      Install and Run Activiti Enterprise

      Read this section in the Activiti Enterprise Getting started guide. And download, install, and start up the Activiti Enterprise server.

       

      Add Apache Camel to Activiti Enterprise

      As mentioned in the introduction, the libraries needed for the Activiti <-> Camel integration are not available in Activiti Enterprise, so we need to install them. More specifically, we need a Camel Integration Engine and Router running that we can send and receive messages via. However, we don’t need to download or run anything as we will include the Camel runtime as part of the Activiti Server .

       

      To get to the Camel libraries that we need download the Apache Camel distribution from this page.

       

      We are going to set up Camel routes that uses the file: endpoint so we need the following libraries copied into the Activiti runtime:

       

      martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/apache-camel-2.18.0/lib/camel-core-2.18.0.jar .

       

      File endpoint:

       

      martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/apache-camel-2.18.0/lib/camel-stream-2.18.0.jar .

       

      Note. for each Camel endpoint that you are using in your route configuration you need to also install the library (or libraries) supporting it. When you start the Activiti server it will tell you if some classes are not found, so you get a heads up before you start running any processes.

       

      Add the Activiti Camel Lib to Activiti Enterprise

      As mentioned in the introduction, the Activiti Camel library is not part of the Enterprise distribution so we need to grab it from a Community release. This is to be able to use the activiti: endpoint, or more specifically component, in a Camel route configuration. This is similar to how we added a library for the file endpoint. The Activiti Camel service task implementation (org.activiti.camel.impl.CamelBehaviorDefaultImpl) is also contained in this lib.

       

      So you need to first download latest version of Activiti Community version 5.x from this page.

       

      Then copy the following library from the Community distribution to the Activiti Enterprise installation:

       

      martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/Downloads/activiti-camel-5.21.0.jar .

       

      Implementing a process with a Camel task

      The following section will go through how to implement a simple process that just has one Camel task that invokes a Camel route.

       

      Define the Activiti Process Model

      The process looks like this (see this section for how to define a new process):

       

      The full process definition XML looks like this:

       

      <?xml version='1.0' encoding='UTF-8'?>

      <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/processdef" xmlns:modeler="http://activiti.com/modeler" modeler:version="1.0en" modeler:exportDateTime="20161023132109074" modeler:modelId="2002" modeler:modelVersion="2" modeler:modelLastUpdated="1477211114281">

        <process id="testCamelTask" name="Test Camel Task" isExecutable="true">

          <documentation>Just testing a process with a Camel task</documentation>

         

          <startEvent id="startEvent1" name="Start">

            <extensionElements>

              <modeler:editor-resource-id><![CDATA[startEvent1]]></modeler:editor-resource-id>

            </extensionElements>

          </startEvent>

         

          <sequenceFlow id="sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D" sourceRef="startEvent1" targetRef="sendMsgToCamel">

            <extensionElements>

      <modeler:editor-resource-id><![CDATA[sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D]]></modeler:editor-resource-id>

            </extensionElements>

          </sequenceFlow>

          <serviceTask id="sendMsgToCamel" name="Send a message to Apache Camel" activiti:type="camel">

            <extensionElements>

              <activiti:field name="camelContext">

                <activiti:string><![CDATA[camelContext]]></activiti:string>

              </activiti:field>

      <modeler:editor-resource-id><![CDATA[sid-6C07BEED-A0AE-4C0D-8A18-AE5F8DE50C61]]></modeler:editor-resource-id>

            </extensionElements>

          </serviceTask>

          <sequenceFlow id="sid-55000554-41F4-4069-810F-07F264D639E6" sourceRef="sendMsgToCamel" targetRef="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9">

            <extensionElements>

      <modeler:editor-resource-id><![CDATA[sid-55000554-41F4-4069-810F-07F264D639E6]]></modeler:editor-resource-id>

            </extensionElements>

          </sequenceFlow>

          <endEvent id="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9" name="End">

            <extensionElements>

      <modeler:editor-resource-id><![CDATA[sid-19B79A92-E86E-4E51-B42C-376798EDDBA9]]></modeler:editor-resource-id>

            </extensionElements>

          </endEvent>

        </process>

        <bpmndi:BPMNDiagram id="BPMNDiagram_testCamelTask">

          <bpmndi:BPMNPlane bpmnElement="testCamelTask" id="BPMNPlane_testCamelTask">

            <bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">

              <omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"/>

            </bpmndi:BPMNShape>

            <bpmndi:BPMNShape bpmnElement="sendMsgToCamel" id="BPMNShape_sendMsgToCamel">

              <omgdc:Bounds height="80.0" width="100.36219727999998" x="240.0" y="138.0"/>

            </bpmndi:BPMNShape>

            <bpmndi:BPMNShape bpmnElement="sid-19B79A92-E86E-4E51-B42C-376798EDDBA9" id="BPMNShape_sid-19B79A92-E86E-4E51-B42C-376798EDDBA9">

              <omgdc:Bounds height="28.0" width="28.0" x="450.0" y="164.0"/>

            </bpmndi:BPMNShape>

            <bpmndi:BPMNEdge bpmnElement="sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D" id="BPMNEdge_sid-3D275FA8-FECC-4E3F-958B-9226193B4D0D">

              <omgdi:waypoint x="130.0" y="178.0"/>

              <omgdi:waypoint x="240.0" y="178.0"/>

            </bpmndi:BPMNEdge>

            <bpmndi:BPMNEdge bpmnElement="sid-55000554-41F4-4069-810F-07F264D639E6" id="BPMNEdge_sid-55000554-41F4-4069-810F-07F264D639E6">

              <omgdi:waypoint x="340.36219728" y="178.0"/>

              <omgdi:waypoint x="449.1306593325" y="178.0"/>

            </bpmndi:BPMNEdge>

          </bpmndi:BPMNPlane>

        </bpmndi:BPMNDiagram>

      </definitions>

       

      The important stuff in the process definition is the process id (testCamelTask) and the Camel service task id (sendMsgToCamel) as these ids will be used later on when you define Camel routes starting with the activiti: endpoint. Note also that the Camel service task has activiti:type="camel", which will pinpoint the actual service class implementation. It is also worth mentioning the camelContext field on the Camel task, it is used to tell Activiti what bean id to look for that contains the Camel Spring context. So we need to match that id later on when configuration the Camel spring context.

       

      So not much is actually needed in the form of process definition to try out the Camel support in Activiti. We however need to implement a number of Java classes and put them on the Activiti server’s classpath. And for this we need an extension project.

       

      Create an Activiti Extension Project with the Camel config and routes

      This section goes through how to generate an Activiti extension project and how to add the  necessary classes to it to support an Activiti -> Camel integration.

       

      Create an Activiti Extension Project

      To set up an Activiti JAR extension project is quite easy. We will assume that you have Java and Maven installed. At the command line do the following to verify that these tools are installed and configured:

       

      martin@gravitonian:~$ mvn --version

      Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T12:57:37+01:00)

      Maven home: /home/martin/apps/apache-maven-3.3.3

      Java version: 1.8.0_91, vendor: Oracle Corporation

      Java home: /usr/lib/jvm/java-8-oracle/jre

      Default locale: en_GB, platform encoding: UTF-8

      OS name: "linux", version: "4.2.0-42-generic", arch: "amd64", family: "unix"

       

      If there is a problem check out the official docs for how to set things up: installing Java, setting JAVA_HOME, installing Maven, setting MAVEN_OPTS.

       

      Now, create a directory to hold your Activiti extension project, for example activiti-jar-camel. Then create a Maven pom.xml file in this directory with the following content:

       

      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

         <modelVersion>4.0.0</modelVersion>
         <groupId>com.activiti.extension</groupId>
         <artifactId>activiti-jar-camel</artifactId>
         <version>1.0-SNAPSHOT</version>
         <name>Activiti Jar Module with Camel integration</name>
         <description>Activiti JAR Module that produces a JAR file with Java extensions such as service task delegates.</description>
         <packaging>jar</packaging>

         <properties>
             <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

             <!-- Properties used in dependency declarations -->
             <activiti.groupId>com.activiti</activiti.groupId>
             <activiti.version>1.5.1</activiti.version>
             <activiti.engine.version>5.21.0.2</activiti.engine.version>

             <!-- Compile with Java 7, default is 5 -->
             <maven.compiler.source>1.7</maven.compiler.source>
             <maven.compiler.target>1.7</maven.compiler.target>
         </properties>

         <dependencies>
             <!-- The main Activiti Enterprise application dependency that brings in all
                  needed classes to compile your customizations -->

             <dependency>
                 <groupId>${activiti.groupId}</groupId>
                 <artifactId>activiti-app-logic</artifactId>
                 <version>${activiti.version}</version>
             </dependency>

             <!-- Testing -->
             <dependency>
                 <groupId>junit</groupId>
                 <artifactId>junit</artifactId>
                 <version>4.11</version>
                 <scope>test</scope>
             </dependency>
         </dependencies>

         <build>
             <resources>
                 <!-- Filter the resource files in this project and
                        do property substitutions -->

                 <resource>
                     <directory>src/main/resources</directory>
                     <filtering>true</filtering>
                 </resource>
             </resources>
             <testResources>
                 <!-- Filter the test resource files in this project and
                      do property substitutions -->

                 <testResource>
                     <directory>src/test/resources</directory>
                     <filtering>true</filtering>
                 </testResource>
             </testResources>
         </build>

         <!--
             SkyVault Maven Repositories
             -->

         <repositories>
             <!-- Activiti Enterprise Edition Artifacts,
                  put username/pwd for server in settings.xml -->

             <repository>
                 <id>activiti-private-repository</id>
                 <url>https://artifacts.alfresco.com/nexus/content/repositories/activiti-enterprise-releases</url>
             </repository>
         </repositories>
      </project>

       

      Then create the following project directory structure:

       

      src/activiti-jar-camel$ tree

      .

      ├── pom.xml

      └── src

          ├── main

          │   ├── java

          │   │   └── com

          │   │       └── activiti

          │   │           └── extension

          │   │               ├── bean

          │   │               ├── camel

          │   │               │   └── route

          │   │               └── conf

          │   │                   └── camel

          │   └── resources

          └── test

              └── java

                  └── com

                      └── activiti

                          └── extension

                              └── bean

       

      The different packages that we use for Java classes are important as only some packages are scanned by Activiti, such as the com.activiti.extension.bean package, which is scanned for Spring beans implementing things like service tasks and task listeners. And the com.activiti.extension.conf package, which is scanned for new Spring context configurations. So it is good practice to use these packages as a starting point.

       

      If you want to look at the source code for this article have a look at this GitHub Project.

       

      Adding the Camel dependencies to the Project

      For us to be able to use any Camel classes in our extension project we need to add a dependency on Camel. Open up the activiti-jar-camel/pom.xml file and add the following dependency:

       

      <dependency>
         <groupId>${activiti.groupId}</groupId>
         <artifactId>activiti-camel</artifactId>
         <version>${activiti.engine.version}</version>
      </dependency>

       

      This will bring in all the needed Camel libraries.

       

      Implement the Camel Spring Context configuration

      By default the Activiti Engine looks for a camelContext bean in the Spring container. The camelContext bean defines the Camel routes that will be loaded by the Camel container and available to Activiti to send and receive messages through.

       

      Activiti looks for Spring context configurations in the com.activiti.extension.conf package. So let’s add a CamelConfiguration bean as follows in this package:

       

      package com.activiti.extension.conf.camel;
      import com.activiti.extension.bean.SomeService;
      import com.activiti.extension.camel.route.CamelTaskRouteBuilder;
      import org.apache.camel.CamelContext;
      import org.apache.camel.impl.DefaultCamelContext;
      import org.apache.camel.impl.SimpleRegistry;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      /**
      * Configure Spring with a new Camel spring context.
      *
      * @author martin.bergljung@alfresco.com
      */

      @Configuration
      public class CamelConfiguration {
         @Bean(name = "camelContext")
         public CamelContext camel() throws Exception{
             SimpleRegistry registry = new SimpleRegistry();
             registry.put("someService", new SomeService());
             CamelContext camelContext = new DefaultCamelContext(registry);
             camelContext.addRoutes(new CamelTaskRouteBuilder());
             camelContext.start();
             return camelContext;
         }
      }

       

      The SimpleRegistry is used to register Spring beans that should be used in Camel routes by using the bean: endpoint, we will do that to show further examples of Camel integration.

       

      The bean registry is passed into the CamelContext when you create it. And then you add the routes that should be active in the context. Note that different Camel Service tasks can use different Camel contexts with different route configurations.

       

      There are two more classes that we need to implement, the CamelTaskRouteBuilder, which defines the Camel routes, and the SomeService bean, which is an example of a Spring bean used in a Camel route.

       

      Note here that Activiti Enterprise uses Spring annotations to define Configuration, Beans, and for wiring beans. And scans for Spring configurations in the com.activiti.extension.conf package or subpackages. There is no XML files with Spring context definitions, and hence no <camelContext entity in Spring XML.

       

      Implement the Camel Routes

      The next thing we need to implement, and the interesting part, are the Camel routes that sets up how messages are routed between different endpoints/systems. Create the following class to do that:

       

      package com.activiti.extension.camel.route;
      import org.apache.camel.LoggingLevel;
      import org.apache.camel.builder.RouteBuilder;

      public class CamelTaskRouteBuilder extends RouteBuilder {

         @Override
         public void configure() throws Exception {
             // Activiti endpoint
             // camel endpoint:processId:camelServiceTaskId?
             String fromActivitiEndPoint = "activiti:testCamelTask:sendMsgToCamel?copyCamelBodyToBody=true";

             // File endpoints
             String dirSource = "/home/martin/temp/";
             String dirTarget = dirSource + "target/";
             String fileName = "some.txt";
             String logMsg = "Testing Apache Camel route invocation from an Activiti Camel service task.";
             String logMsg2 = "Testing Apache Camel route invocation from a file.";
             String fromFileEndpoint = String.format("file://%s?fileName=%s&noop=true", dirSource, fileName);
             String toFileEndpoint = String.format("file://%s?fileName=%s", dirTarget, fileName);
             String toBeanEndpoint = "bean:someService?method=process";
             from(fromActivitiEndPoint).log(LoggingLevel.INFO, logMsg).to(toBeanEndpoint);
             from(fromFileEndpoint).log(LoggingLevel.INFO, logMsg2).to(toFileEndpoint);
         }
      }

       

      This class sets up two Camel routes, one that will be triggered by the Activiti Camel Service task:

       

      from(fromActivitiEndPoint).log(LoggingLevel.INFO, logMsg).to(toBeanEndpoint);

       

      and one that will be triggered via a file in a directory:

       

      from(fromFileEndpoint).log(LoggingLevel.INFO, logMsg2).to(toFileEndpoint);

       

      The Activiti Camel Task will trigger a route that writes a log message and then calls a method in the SomeService bean. We have not yet implemented this bean and this is the bean that was added to the Camel ServiceRegistry.

       

      The interesting part here is how we set up the Activiti Camel endpoint:

       

      activiti:testCamelTask:sendMsgToCamel

       

      It starts with the Activiti Camel component/endpoint identifier (i.e. activiti:), then we specify the process id (testCamelTask), and then the Camel Service task id (sendMsgToCamel).

       

      Implement the Service Bean

      One of our Camel routes uses a bean call so we need to implement this Camel bean, which is not the same thing as a Spring bean. Here is how it looks like:

       

      package com.activiti.extension.bean;

      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

      public class SomeService {
         private static Logger logger = LoggerFactory.getLogger(SomeService.class);

         public String process() {
             logger.info("Doing some processing...");
             return "Finished processing!";
         }
      }

       

      This bean is not doing much more than writing a simple log message. But we should see this message when the Camel route is invoked via the Activiti Camel Service task.

       

      This bean is defined in the com.activiti.extension.bean package, which means it could easily be converted to a Spring bean used by other service tasks in the process, the only thing we would need to do is add the @Component annotation. The com.activiti.extension.bean package is automatically scanned by Activiti for beans.

       

      Build the Extension Project JAR

      Standing in the project directory, execute the following command to build the project and create a JAR with all the necessary classes:

       

      ~/src/activiti-jar-camel$ mvn clean install

       

      Run the Process with a Camel Task

      This section shows (or give links) to how to run the process with the Camel task.

       

      Create a file to trigger one of the routes

      One of the routes is triggered via a file called some.txt located in a directory called /home/martin/temp. Create this directory and the file. Or change the filename and directory to something else in the CamelTaskRouteBuilder class.

       

      Copy the Extension Project JAR into the Activiti Server and Restart

      Before we can use the process definition with the Camel Task we need to install the extension JAR with the necessary classes:

       

      martin@gravitonian:/opt/activiti15/tomcat/webapps/activiti-app/WEB-INF/lib$ cp ~/src/activiti-jar-camel/target/activiti-jar-camel-1.0-SNAPSHOT.jar .

       

      Then restart as follows:

       

      martin@gravitonian:/opt/activiti15$ /opt/activiti15/tomcat/bin/catalina.sh stop

      martin@gravitonian:/opt/activiti15$ /opt/activiti15/h2/stop-h2.sh

      martin@gravitonian:/opt/activiti15$ ./start-activiti.sh

       

      When we restart the Activiti server it should log a message triggered via the file from endpoint:

       

      02:18:41,069 [activiti-app-rest-Executor-1] INFO  com.activiti.service.runtime.integration.alfresco.AlfrescoOnPremiseTicketService  - The size of this cache is determined by the 'cache.alfresco-tickets.max.size' and 'cache.alfresco-tickets.max.age' property.

      02:18:41,230 [Camel (camel-1) thread #0 - file:///home/martin/temp/] INFO  route2  - Testing Apache Camel route invocation from a file.

      23-Oct-2016 14:18:43.841 INFO [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive /opt/activiti15/tomcat/webapps/activiti-app.war has finished in 40,410 ms

       

      Running the Process with the Camel Task

      For information on how to run a process in Activiti Enterprise have a look at this page. Basically, you need to embed your business process model in an Application before you can use it, which is described here.

       

      When we run the process we should see the following log messages:

       

      02:15:17,243 [http-nio-8080-exec-6] INFO  route1  - Testing Apache Camel route invocation from an Activiti Camel service task.

      02:15:17,248 [http-nio-8080-exec-6] INFO  com.activiti.extension.bean.SomeService  - Doing some processing...

       

      Start an Activiti Process from Camel route

      It is possible to kick off an Activiti process from a Camel route. You just need to know the process id and then define the route something like this:

       

      // Starting an Activiti process
      from("direct:start")
      .log(LoggingLevel.INFO, "Testing starting workflow from Camel route")
      .to("activiti:testCamelTask");

       

      In this case the process id is the same we used in this article (i.e. testCamelTask).