About Joram Barrez

Joram is an all-around software engineer with a keen interest in anything that is slightly related to software development. He left JBoss for starting the Activiti project together with Tom Baeyens (also ex-JBoss) at Alfresco .Besides Activiti, he is also heavily involved with iOS development and the workflow features for the Alfresco Mobile app.

Advanced scripting in Activiti: Custom Configuration Injection

The scripting task is probably one of the ‘oldest’ classes in the Activiti code base, but I think it is still underused by many. The (perceived?) downsides are of course performance (interpretation vs compilation) and less support from IDE perspective.

However, the benefits (imo) outweigh this:

  • Scripts are defined in the process xml itself. No more worries about versioning and having to juggle with libs on the classpath.
  • What we’ve seen in the past is that less technical skilled people dare to try scripts. But never Java.

Anyway, what few people know or have realized is that you can do really awesome and advance stuff in scripts in Activiti. Since such a script is executed within the process engine, you have access to everything the engine is capable of. Yes … everything… which makes it both a very powerful but also (potential) dangerous thing (if you don’t know what you’re doing).

Let me walk you through such an example. I like to call it ‘custom configuration injection’ as a concept, because it effectively allows you to add custom logic at runtime which alters process execution significantly. If you have a cooler name for it, please let me know.

All code can be found on my Github Page: https://github.com/jbarrez/activiti-advanced-scripting

awesome-code-648x303

The use case

Now what is this thing I want to do. Well, I want to have a process that, when executed

  • Adds a ‘task completion event handler’ to every user task that is executed
  • This event handler must fire a custom event off to a remote URL, where potentially a event processor is doing its stuff

So basically, we want to fire off custom events to some remote URL whenever a task gets completed. A good use case for this could be Business Intelligence reporting/Complex event processing, eg with something like Esper.

Screen-Shot-2013-07-23-at-10.03.112

The first version

The first cut of this functionality can be found at https://github.com/jbarrez/activit-advanced-scripting/blob/master/src/test/resources/org/activiti/test/my-process.bpmn20.xml. When this process is executed, the following happens:

var config = Context.getProcessEngineConfiguration();
var bpmnParser = config.getBpmnParser();

We simply fetch the current ProcessEngineConfiguration instance. We fetch the BpmnParser instance from this configuration, as we will want to change the general user task parsing for the whole engine.

Next, we build the script:

var script = "";
script = script + "importPackage(java.net);";
script = script + "importPackage(java.io);";
script = script + "var url = new URL('http://localhost:8182/echo');";
script = script + "var connection = url.openConnection();";
script = script + "connection.setRequestMethod('POST');";
script = script + "connection.setDoOutput(true);";
script = script + "var outputStream = new BufferedOutputStream(connection.getOutputStream());";
script = script + "outputStream.write(new java.lang.String(\"{'eventType':'task-complete'}\").bytes);";
script = script + "outputStream.flush();";
script = script + "connection.connect();";
script = script + "var respCode = connection.getResponseCode();";
script = script + "if (respCode != 200) ";
script = script + "println('Response code : ' + respCode);";
script = script + "outputStream.close();";
script = script + "connection.disconnect();";

This is obviously not the most efficient way to do this, but it sure shows the details of what happens. The message ‘eventType:task-complete’ is send to the localhost:8182 url through standard java.net and java.io classes.

The tricky part comes next:

var handler = new ExecuteScriptOnTaskCompleteBpmnParseHandler("JavaScript");
handler.setUserTaskCompleteScript(script);
bpmnParser.getBpmnParserHandlers().addHandler(handler);

// reset the deployment cache such that the new listener gets picked up on a new redeploy
config.getProcessDefinitionCache().clear();

Here we add a BpmnParseHandler class to the engine configuration. The parse handler will add the execution of the script defined above to every receival of the ‘task complete event’ send out by the engine. This parse handler kicks in every time a user task is parsed, which effectively adds our ‘send-event-to-remote-service’ to every user task now happening in your Activiti environment!

There is a unit test to see how this works: https://github.com/jbarrez/activiti-advanced-scripting/blob/master/src/test/java/org/activiti/test/ExecuteScriptInProcessTest.java. In the test, we setup a very simple ‘echo service’ which simply prints out whenever such an event is received. If you run it in your IDE, you’ll see something like this:

Screen-Shot-2013-07-23-at-09.53.00

But we can do better

But we can do better. Check the following code.

var handler = new ExecuteScriptOnTaskCompleteBpmnParseHandler("JavaScript");
handler.setUserTaskCompleteScript("http://localhost:8182/scripts/task-complete.js");
handler.setExecuteScriptInJob(true);
bpmnParser.getBpmnParserHandlers().addHandler(handler);

// Update the configuration to use the correct job handler
var jobHandler = new ExecuteScriptJobHandler();
config.getJobHandlers().put(jobHandler.type,jobHandler);

This code does the same as in the previous section, ie. attaching a listener for ‘complete’ events to every user task. However, this implementation:

  • Executes the script asynchronously
  • Does not define the script in the process xml, but it is fetched from a remote url
  • Updates the job handler configuration

If you ask me, that’s pretty awesome! So this means that the actual sending of a message to the remote service is not impacting the execution performance of your process instance. Obviously, from here you can go crazy and add persistent queues and all that fanciness. And on top of that, the script is always fetched from a remote server. If you want to update the logic that is executed, simply change the script that is returned. This means you can impact process execution AT RUNTIME without touching the actual process.

There is a unit test for this at https://github.com/jbarrez/activiti-advanced-scripting/blob/master/src/test/java/org/activiti/test/ExecuteScriptWithJobTest.java

If you run this test,  you’ll see the following. Note that we host the completion script as static file called ‘task-complete.js’ on the test server.

Screen-Shot-2013-07-23-at-09.50.362-1024x171

In the test, you can see we have to execute the async job specifically to see the output of the test.

Caveat

On small caveat here: when the process engine reboots, the configuration will be reloaded from config file. Hence, the process from above that injects custom logic is not added. However, this can easily be done by using a ProcessEngineLifeCycleListener implementation that executes a process definition of a certain category after the engine has booted up. If you for example give all these processes ‘config-processes’ as category, they can easily be executed on bootup.

Conclusion

Scripting in BPMN 2.0 processes is a very powerful feature. It allows you to change process execution engine-wide in a matter of a few lines. Of course, all the code above can be done with Java. But the examples above use nothing more than standard BPMN 2.0 and the javascript engine that is bundled with every JDK install.

Thanks for reading. Happy coding!
 

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

Leave a Reply


six + = 10



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy | Contact
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close