Create administration tools

Discover how to create tools to administer your Bonita system using Maven and Java.

Getting started

The example proposes to list the failed tasks, and is wrapped in a script so that it can be executed in an environment with no window manager.

You need to be familiar with building a Maven project with Bonita as a dependency. You also need to be familiar with using the Bonita Engine APIs. If this is not the case, follow this tutorial. The Engine API overview contains a list of the APIs with a short description of each one. For details of the methods available in the Bonita Engine APIs, see the Javadoc.

This example uses the Bonita Engine Java APIs but there is also a REST API.

In this example, the Maven project has the following settings:

  • Group Id: org.mycompany.bonita

  • Artifact Id: bonita-cli

You must have a running Bonita system. The simplest way to do this is to run a Bonita Tomcat bundle locally. You also need a user: in this example the user has username admin and password secret.

Write a class that lists failed tasks

Bonita provides a client to leverage the Java APIs. This is the easiest starting point for an application, so this is what we will use in the example.

Set up your Maven project with a maven dependency to bonita-client. Then create a class to execute the business logic. In this example, the class is called ListFailedTasksCmd. It implements Callable<Void>, and calls methods to log in the API, then to execute the business logic, and finally to log out from the API.

public Void call() throws Exception {
    login();
    try {
        printFailedTasks();
    } finally {
        logout();
    }
}

The business logic of this example gets a list of the failed tasks and prints them to standard output. The maximum number of failed activities to retrieve is provided by the class field nbTodisplay. The method printFailedTasks is doing the actual job of searching for activities whose state is FAILED.

Like for any methods in the API listing elements, the searchActivities method takes paging information to limit the number of elements to retrieve. This prevents clients from retrieving too much data at the same time and filling up the whole memory. This is a common way to avoid users getting OutOfMemory errors. It is better to make several successive calls to the API than to retrieve too much data.
protected void printFailedTasks() throws BonitaHomeNotSetException,
    		ServerAPIException, UnknownAPITypeException, SearchException {

ProcessRuntimeAPI taskAPI = (ProcessRuntimeAPI) TenantAPIAccessor.getProcessAPI(session);
SearchOptions failedSearchOptions = new SearchOptionsBuilder(0,nbTodisplay)
    .filter(ActivityInstanceSearchDescriptor.STATE_NAME, ActivityStates.FAILED_STATE)
    .done();

SearchResult<ActivityInstance> failedTasks = taskAPI.searchActivities(failedSearchOptions);
print(failedTasks);
}

See the complete source code for this class is in ListFailedTasksCmd.java.

Create a main class

Let’s create a CLI.java class that will be the entry point of our application. The CLI will simply delegate to our ListFailedTasksCmd the responsibility to fetch failed tasks and display them on the standard output. The CLI will take as input parameters the username, password and the maximum number of failed tasks to display.

public static void main(String[] args) {
  if(args.length != 3) {
    throw new IllegalArgumentException("Usage: you should provide <username> <password> <number of tasks to display>");
  }

  try {
       new ListFailedTasksCmd(args[0], args[1], Integer.parseInt(args[2])).call();
  } catch (Exception e) {
	   e.printStackTrace();
  }
}

Prepare packaging

As we have seen, the tool has several dependencies required to be executed. This means that our classes should be packaged along with these dependencies in a single bundle. To do this, build a zip with all the resources that the system will require to run the CLI.

Declare an Assembly

Create an assembly.xml file at the root to describe the final packaging.

<assembly 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/xsd/assembly-1.0.0.xsd">
        <id></id>
        <formats>
                <format>zip</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <fileSets>
                <fileSet>
                        <outputDirectory>/</outputDirectory>
                        <directory>src/main/resources</directory>
                        <includes>
                                <include>**</include>
                        </includes>
                        <fileMode>744</fileMode>
                </fileSet>
        </fileSets>
        <dependencySets>
                <dependencySet>
                        <outputDirectory>/lib</outputDirectory>
                </dependencySet>
        </dependencySets>
</assembly>

This assembly gathers all the dependencies into a lib folder along with all the content of the src/main/resources before zipping the whole collection. At the end we should get the following structure in a zip:

|-bonita/
|-lib/
   |- bonita-cli.jar
   |-*.jar

Update the pom.xml file to call the assembly when building the project

<properties>
        <zipName>bonita-cli-${project.version}</zipName>
        <folderToZip>${project.build.directory}</folderToZip>
</properties>
...
<build>
<plugins>
...
<plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <executions>
                <execution>
                        <id>generate-package</id>
                        <phase>package</phase>
                        <goals>
                                <goal>single</goal>
                        </goals>
                        <configuration>
                                <ignoreDirFormatExtensions>true</ignoreDirFormatExtensions>
                                <finalName>${zipName}</finalName>
                                <descriptors>
                                        <descriptor>assembly.xml</descriptor>
                                </descriptors>
                        </configuration>
                </execution>
        </executions>
</plugin>
…
</plugins>
</build>

The maven-assembly-plugin enables us to generate the zip as described in assembly.xml file with the specified zipName, e.g. Bonita-CLI-1.0.0.

Make the bonita-cli.jar executable

In order to launch the expected class, CLI.java, edit the pom.xml and add the following lines. This will create a manifest during the build of the Maven project in the jar file which contains the main class.

<build>
<plugins>
        ...
                <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>2.4</version>
                        <configuration>
                                <archive>
                                        <manifest>
                                                <addClasspath>true</addClasspath>
<mainClass>org.mycompany.bonita.cli.CLI</mainClass>
                                                </manifest>
                                        </archive>
                                        <excludes>
                                                <exclude>*.bat</exclude>
                                                <exclude>*.sh</exclude>
                                        </excludes>
                                </configuration>
                        </plugin>
                        ...
                </plugins>
        </build>

Build and test

Right click on the project, select Run As > Maven Install. The output Bonita-CLI-1.0.0.zip is located in /target. Open a console, go to the target folder, and unzip Bonita-CLI-1.0.0.zip. Before executing make sure to configure the bonita client to point the bonita engine by editing ${bonita.home}/engine-client/conf/bonita-client-custom.properties. For example:

# HTTP
org.bonitasoft.engine.api-type = HTTP
server.url = http://localhost:8080
application.name = bonita

To test our main class, execute the following command:

  • On Linux:java -jar lib/*.jar admin secret 10 -Dbonita.home=\pwd`/bonita`

  • On Windows:java -Dbonita.home=bonita -jar lib/bonita-cli-1.0.0.jar admin secret 10

These commands are cumbersome, so the next section explains how to create a script to hide the complexity.

Wrap in a Shell script

In the resources folder of your project, create a new file bonita-cli.sh or bonita-cli.bat:

bonita-cli.sh:

#!/bin/sh
java -Dbonita.home=\`pwd\`/bonita -jar lib/bonita-cli-*.jar $*

bonita-cli.bat:

java -Dbonita.home=bonita -jar lib/bonita-cli-1.0.0.jar %*

Build the whole project again. When the build completes, the zip file should contain the following structure:

Zip file structure

Run the appropriate script:

  • On Linux:./bonita-cli.sh admin secret 10

  • On Windows:bonita-cli.bat admin secret 10

Here is an example of output:

List of activities:(1 item(s) found)
  State   On      id      Name
  failed  16:11   100     Review Git Pull Request from: N Chabanoles

Complete code example

package org.mycompany.bonita.cli.cmd;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.concurrent.Callable;

import org.bonitasoft.engine.api.ProcessRuntimeAPI;
import org.bonitasoft.engine.api.TenantAPIAccessor;
import org.bonitasoft.engine.bpm.flownode.ActivityInstance;
import org.bonitasoft.engine.bpm.flownode.ActivityInstanceSearchDescriptor;
import org.bonitasoft.engine.bpm.flownode.ActivityStates;
import org.bonitasoft.engine.exception.BonitaHomeNotSetException;
import org.bonitasoft.engine.exception.SearchException;
import org.bonitasoft.engine.exception.ServerAPIException;
import org.bonitasoft.engine.exception.UnknownAPITypeException;
import org.bonitasoft.engine.platform.LoginException;
import org.bonitasoft.engine.search.SearchOptions;
import org.bonitasoft.engine.search.SearchOptionsBuilder;
import org.bonitasoft.engine.search.SearchResult;
import org.bonitasoft.engine.session.APISession;

public class ListFailedTasksCmd implements Callable<Void> {
    private static final String ROW_SEPARATOR = "\n";
	private static final String COL_SEPARATOR = "\t";

	private String userName;
	private String password;
	private APISession session;
	private int nbTodisplay;

	public ListFailedTasksCmd(String user, String pass, int nbTodisplay) {
		userName = user;
		password = pass;
		this.nbTodisplay = nbTodisplay;
	}

	public Void call() throws Exception {
		login();
		try {
			printFailedTasks();
		} finally {
			logout();
		}
		return null;
	}

	protected void printFailedTasks() throws BonitaHomeNotSetException,
			ServerAPIException, UnknownAPITypeException, SearchException {
		ProcessRuntimeAPI taskAPI = (ProcessRuntimeAPI) TenantAPIAccessor
				.getProcessAPI(session);
		SearchOptions failedSearchOptions = new SearchOptionsBuilder(0,
				nbTodisplay).filter(
				ActivityInstanceSearchDescriptor.STATE_NAME,
				ActivityStates.FAILED_STATE).done();
		SearchResult<ActivityInstance> failedTasks = taskAPI
				.searchActivities(failedSearchOptions);
		print(failedTasks);
	}

	protected void print(SearchResult<ActivityInstance> failedTasks) {
		List<ActivityInstance> activities = failedTasks.getResult();
		StringBuffer line = new StringBuffer();
		// Header
		line.append(ROW_SEPARATOR).append("List of activities:")
				.append("(" + failedTasks.getCount() + " item(s) found)")
				.append(ROW_SEPARATOR);
		line.append(COL_SEPARATOR).append("State").append(COL_SEPARATOR)
				.append("On").append(COL_SEPARATOR).append("id")
				.append(COL_SEPARATOR).append("Name");
		line.append(ROW_SEPARATOR);
		// Display in a table layout
		for (ActivityInstance activityInstance : activities) {
			line.append(COL_SEPARATOR);
			line.append(activityInstance.getState()).append(COL_SEPARATOR);
			line.append(
					new SimpleDateFormat("HH:MM").format(activityInstance
							.getReachedStateDate())).append(COL_SEPARATOR);
			line.append(activityInstance.getId()).append(COL_SEPARATOR);
			line.append(activityInstance.getName()).append(COL_SEPARATOR);
			line.append(ROW_SEPARATOR);
		}
		System.out.println(line.toString());
	}

	private void logout() {
		if (session != null) {
			try {
				TenantAPIAccessor.getLoginAPI().logout(session);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	protected void login() throws LoginException, BonitaHomeNotSetException,
			ServerAPIException, UnknownAPITypeException {
		this.session = TenantAPIAccessor.getLoginAPI()
				.login(userName, password);
	}
}