top of page
Search
Writer's pictureRyan Johnston

Custom Classes & Plugins Using Camunda BPM Run

Camunda BPM Run ("Camunda Run") is an alternate, lighter-weight distribution of Camunda BPM based on Spring Boot. While Camunda's smart packaging makes it easy to make certain configuration changes to the product using configuration files, others aren't possible out of the box. This blog post provides two examples illustrating how Camunda Run can be extended in an almost limitless way - to meet your specific business requirements - using Spring Boot's built-in features and capabilities.


Note that Camunda Run has both an Apache-licensed, "Community Edition" version and an "Enterprise Edition" version; this article covers the Community Edition version, though everything herein should be applicable to both versions.


A Little Background on Spring Boot Executable JAR Files


Camunda Run - at its core - uses a Spring Boot executable JAR file. These executable JAR files are quite common in the industry and are used to make it easier to deploy Java projects, to make it possible to have nearly one-click execution of both custom code and the application servers on which they run. But can these self-contained, executable JAR files be extended? Yes!


In brief, it's possible to pick up additional, supporting JAR files when executing a Spring Boot executable JAR file. This requires two prerequisites:


  1. The Main-Class in the executable JAR file's META-INF/MANIFEST.MF file must be org.springframework.boot.loader.PropertiesLauncher and

  2. The supporting JAR file must have a META-INF/spring.factories file that points to an @Configuration-annotated class that will instruct Spring Boot how to use the JAR file's classes for configuration & bootstrapping.


If you have a Spring Boot executable JAR file from another source and want to leverage an external dependency, you can get started using the guidelines above, as they aren't specific to Camunda Run.


As noted in the opening paragraph, Camunda Run makes it very easy to make certain configuration changes. These changes can be made within plain-text YAML files; please see the link at the beginning of this blog entry for more information.


Making Custom JavaDelegate Implementations Available to Camunda Run


Our first example will illustrate how we can build a custom JavaDelegate implementation and then make that implementation available within a JAR file to Camunda Run. We also need a model that references the delegate, and we'll use the following process model:



This process model was used in a previous blog entry entitled JSON Handling in Camunda BPM & Flowable; the model illustrates how JSON objects can be created, stored & accessed using Camunda Spin in Camunda BPM. Within that model, in the step entitled Create Car Object using Java, we reference the following JavaDelegate implementation:



package co.summit58.cam.run.delegates;

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
import org.camunda.spin.Spin;
import org.camunda.spin.json.SpinJsonNode;
import org.springframework.stereotype.Component;

@Component
public class CreateCarObjectInJSON implements JavaDelegate {

	@Override
	public void execute(DelegateExecution execution) throws Exception {
		SpinJsonNode sierra = Spin.JSON("{}");
		sierra.prop("displacement", 6.2);
		sierra.prop("engineType", "V8");
		sierra.prop("forcedInduction", false);
		sierra.prop("horsepower", 420);
		sierra.prop("year", 2018);
		sierra.prop("make", "GMC");
		sierra.prop("model", "Sierra");

		execution.setVariable("sierraJava", sierra);
	}

}

In order to point Spring Boot to this JavaDelegate implementation for dependency injection, we need an @Configuration-annotated class, and we'll use the following class*:



package co.summit58.cam.run.resourceconfig;

import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@ConditionalOnBean(type = "org.camunda.bpm.engine.ProcessEngine")
public class ResourceConfiguration {
	
	@Configuration
	@ComponentScan("co.summit58.cam.run.delegates")
	public static class EmbeddedConfiguration {
		
	}
	
}

The structure of this class is identical to the structure of the @Configuration class that we use in the JAR files that we deploy alongside the competing Flowable product's Spring Boot executable WAR files. That's important, because it illustrates that we aren't doing anything "magical" for Camunda Run here; it's standard Spring Boot, and the way these extension JAR files are made available is the same.


This @Configuration class is referenced by the spring.factories file, which has to be placed in the JAR file's META-INF directory. The spring.factories file looks like this:



org.springframework.boot.autoconfigure.EnableAutoConfiguration=co.summit58.cam.run.resourceconfig.ResourceConfiguration

The next step is then to JAR these together along with any required dependencies that aren't already available in Camunda Run. This can be done by creating an "uber" JAR, a good explanation for which is available here.


Once that JAR has been created, copy/paste it to the configuration/userlib directory within your Camunda Run directory and restart Camunda Run. If you run an instance of the model referenced above, you'll see - if you've set it up correctly - that Camunda Run has picked up the JavaDelegate implementation and that it works as desired.


Advanced Process Engine Configuration in Camunda Run


As noted above, it will often be necessary to make configuration changes that can't be made via Camunda Run's included YAML files. In those situations, you'll need a custom plugin. How can I create and use one of those in Camunda Run? We'll provide a straightforward example below.


Let's say - for demo purposes - that you want to source a custom JavaScript file (named bpmn.js in this case) from the classpath containing a custom function. This custom function might look like this:



function sayHello(name) {
  print('Hello ' + name + '!!');
}

(Yes, it's simple. This is a demo! 🙂)


To reference this file/function and call it from within Script Tasks, you would need a custom ScriptEnvResolver, and this can be added using a ProcessEnginePlugin. Here's our custom ScriptEnvResolver:



package co.summit58.cam.run.plugins;

import org.camunda.bpm.engine.impl.scripting.env.ScriptEnvResolver;
import org.camunda.commons.utils.IoUtil;

import java.io.InputStream;

public class CustomBPMNScriptResolver implements ScriptEnvResolver {

    private final String JS_FILENAME = "bpmn.js";

    @Override
    public String[] resolve(String language) {
        if(language.toLowerCase().equals("javascript") || language.toLowerCase().equals("ecmascript")) {
            InputStream bpmnCustomJSStream = CustomBPMNScriptResolver.class.getClassLoader()
                    .getResourceAsStream(JS_FILENAME);
            String bpmnCustomJS = "";
            if(bpmnCustomJSStream != null)
                bpmnCustomJS = IoUtil.inputStreamAsString(bpmnCustomJSStream);

            return new String[] { bpmnCustomJS };
        }
        else
            return null;
    }
}

And we need a ProcessEnginePlugin that will make the necessary configuration change:



package co.summit58.cam.run.plugins;

import org.camunda.bpm.engine.ProcessEngine;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;
import org.springframework.stereotype.Component;

@Component
public class CustomBPMNScriptPlugin implements ProcessEnginePlugin {

    @Override
    public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {

    }

    @Override
    public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
        processEngineConfiguration.getEnvScriptResolvers().add(new CustomBPMNScriptResolver());
    }

    @Override
    public void postProcessEngineBuild(ProcessEngine processEngine) {

    }

}

To pick up this custom ProcessEnginePlugin, we'll need to add its package to our list of packages for scanning in our ResourceConfiguration class, which is referenced above. Here's a modified version of that class:



package co.summit58.cam.run.resourceconfig;

import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@ConditionalOnBean(type = "org.camunda.bpm.engine.ProcessEngine")
public class ResourceConfiguration {
	
	@Configuration
	@ComponentScan(basePackages = {"co.summit58.cam.run.delegates", "co.summit58.cam.run.plugins"})
	public static class EmbeddedConfiguration {
		
	}
	
}

To test this, you can use a process model that looks similar to this one:



The Script Task references the following JavaScript code:



sayHello('Ryan');

Closing Thoughts


We hope that this blog entry will be helpful for those who might be struggling to make advanced customizations to Camunda BPM Run. As we've illustrated above, Camunda Run's Spring Boot foundation makes it possible to extend the product in just about any way you can imagine; given that, a case could be made that Camunda Run might be a superior distribution of Camunda BPM for most customer use cases.


If you have any additional questions or need help with your use of Camunda Run, please don't hesitate to reach out to us at info@summit58.co.


* - This class was originally authored based on information from Flowable's guide here: https://flowable.com/open-source/docs/bpmn/ch14-Applications/#using-spring-boot-auto-configuration.

1,426 views0 comments

Recent Posts

See All

Comments


bottom of page