jbpm

Deploying RHPAM KJar on Top of Spring Boot and Integrate It To Business Central

Red Hat Process Automation Manager, or RHPAM, is an open-source business process management (BPMN 2.0) and a low-code development platform. RHPAM has extensible business nodes or plugins and is a pioneer in Business rules engine development, which uses Drools by using drool language or drl language and middleware applications.

In this article here, we are trying to deploy a BPMN workflow project as a jar file, deploy it into Spring Boot, and connecting it to RHPAM Business Central for Monitoring.

So lets start by creating a basic workflow, for this example im using Visual Studio Code with BPMN Editor extension.

Full code for it can be cloned from below Github url,

https://github.com/edwin/rhpam-hello-world-example

Run below command to build our workflow into Jar file, and install it into our local Maven repository,

$ mvn clean install

Next is lets create our Spring Boot project, and we can start with a Maven pom.xml where we can import our BPMN Jar there

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-and-rhpam</artifactId>
    <version>1.0.0</version>
    <name>spring-boot-and-rhpam</name>
    <description>Demo deploying BPMN on Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <narayana.version>5.6.4.Final</narayana.version>

        <kjar.version>1.6.0</kjar.version>
        <kie.version>7.53.0.Final</kie.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-server-spring-boot-starter-jbpm</artifactId>
            <version>${kie.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie.server</groupId>
            <artifactId>kie-server-controller-websocket-client</artifactId>
            <version>${kie.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie.server</groupId>
            <artifactId>kie-server-client</artifactId>
            <version>${kie.version}</version>
        </dependency>

        <!-- kjar here -->
        <dependency>
            <groupId>com.edw</groupId>
            <artifactId>Project01</artifactId>
            <version>${kjar.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

And we can put all configurations on application.properties, including database and BusinessCentral connectivity

## spring boot endpoint
server.address=127.0.0.1
server.port=8080

cxf.path=/rest

# kie-server
kieserver.serverId=kie-server-project01
kieserver.serverName=kie-server-project01
kieserver.location=http://127.0.0.1:8080/rest/server

kieserver.username=kieserver
kieserver.password=password

# url for BusinessCentral
kieserver.controllers=ws://127.0.0.1:8090/business-central/websocket/controller

kieserver.drools.enabled=true
kieserver.dmn.enabled=true
kieserver.jbpm.enabled=true
kieserver.jbpmui.enabled=true
kieserver.casemgmt.enabled=true
kieserver.scenariosimulation.enabled=true

# Dedicated jBPM properties
jbpm.executor.enabled=false

# data source
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.url=jdbc:mysql://localhost:3306/db_rhpam
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=org.apache.tomcat.jdbc.pool.XADataSource

# hibernate configuration
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.show_sql=false
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

# transaction manager configuration
spring.jta.narayana.transaction-manager-id=1

narayana.dbcp.enabled=true
narayana.dbcp.maxTotal=20

# kjar
kjar.name=project01
kjar.groupid=com.edw
kjar.artifactid=Project01
kjar.version=1.6.0

And 2 Java classes, one is for Main class and another one for Security and access right.

package com.edw;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.kie.server.api.marshalling.MarshallingFormat;
import org.kie.server.api.model.KieContainerResource;
import org.kie.server.api.model.KieContainerStatus;
import org.kie.server.api.model.ReleaseId;
import org.kie.server.api.model.ServiceResponse;
import org.kie.server.client.KieServicesClient;
import org.kie.server.client.KieServicesConfiguration;
import org.kie.server.client.KieServicesFactory;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }

    @Value("${kieserver.username}")
    private String user;

    @Value("${kieserver.password}")
    private String password;

    @Value("${kieserver.location}")
    private String url;

    @Value("${kjar.name}")
    private String kjarName;

    @Value("${kjar.groupid}")
    private String kjarGroupid;

    @Value("${kjar.artifactid}")
    private String kjarArtifactId;

    @Value("${kjar.version}")
    private String kjarVersion;

    @Bean
    CommandLineRunner deployAndValidate() {
        return new CommandLineRunner() {
            public void run(String... strings) throws Exception {
                KieServicesConfiguration config = KieServicesFactory.newRestConfiguration(url, user, password, 60000);
                config.setMarshallingFormat(MarshallingFormat.JSON);

                KieServicesClient client = KieServicesFactory.newKieServicesClient(config);
                KieContainerResource kContainer = new KieContainerResource();
                kContainer.setContainerId(kjarName);
                kContainer.setReleaseId(new ReleaseId(kjarGroupid, kjarArtifactId, kjarVersion));

                ServiceResponse<KieContainerResource> resp = client.createContainer(kjarName, kContainer);
                KieContainerStatus status = resp.getResult().getStatus();
                if (!KieContainerStatus.STARTED.equals(status)) {
                    throw new IllegalStateException();
                }
            }
        };
    }
}
package com.edw.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration("kieServerSecurity")
@EnableWebSecurity
public class KieSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/rest/server*").authenticated()
                .and()
                .httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        auth.inMemoryAuthentication()
                .withUser("kieserver").password(encoder.encode("password")).roles("kie-server");

        auth.inMemoryAuthentication()
                .withUser("wbadmin").password(encoder.encode("wbadmin")).roles("kie-server");
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedMethods(Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(),
                HttpMethod.POST.name(), HttpMethod.DELETE.name(), HttpMethod.PUT.name()));
        corsConfiguration.applyPermitDefaultValues();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }
}

To see whether our BPMN has been deployed or not, we can run below CURL command

$ curl -kv http://kieserver:password@localhost:8080/rest/server/containers

Next, is to start Business Central. It’s basically a JBoss application which runs on port 8090. We can start it by running below command,

$ ./standalone.sh

If our Spring Boot successfully connected to Business Central, we can see the result on Menu > Deploy > Execution Servers.

We can do some sample transactions by using below CURL command,

$ curl -kv http://kieserver:password@localhost:8080/rest/server/containers/project01/processes/Project01.Business01/instances -H 'Content-Type: application/json' --data-raw '{
    "application": {
        "com.edw.project01.User": {
            "age": 37,
            "name":"edwin"
        }
    }
}'

A succesful API call shall gives an Integer as a result, which we can see on Business Center’s UI. Go to Menu > Process Instances > Completed, and it shall display list of Process Instances with its detail.

Code for this article can be access on below Github url,

https://github.com/edwin/spring-boot-and-rhpam

How to Fix Error “Variable content was trimmed as it was too long” on RHPAM

Just yesterday i got a very unique error,

Variable content was trimmed as it was too long (more than 255 characters)

I can see that this error happens due to RHPAM saves the content of each object on database, but have a 255 character as limitation. And for this case, my object content length is more than 255 characters.

Workaround for this is we need to alter database generated by RHPAM and for example below, im changing it from 255 into 5000 characters.

alter table VariableInstanceLog modify column value varchar(5000);

alter table VariableInstanceLog modify column oldValue varchar(5000);

And need to put below parameter runtime,

-Dorg.jbpm.var.log.length=5000

For a RHPAM deployment on Openshift, we can embed below parameters on RHPAM DeploymentConfig,

	- name: JAVA_OPTS_APPEND
	  value: '-Dorg.jbpm.var.log.length=5000'

Restart RHPAM, and we can see now RHPAM can handle object with length up to 5000 characters.