Create your first project with the Web REST API and Maven

Product version:
6.5, 6.4, 6.3
Product edition:
  • Community
Get the PDF version

This page contains an example of how to create and run a process using the Web REST API in a Maven project. It assumes that you are a Java programmer familiar with using Maven. It shows how to initialize the Bonita platform Community edition with a Java program that uses the Web REST API, and then create and execute a process. The example was created for the Bonita BPM Community edition and tested on Linux.

There is also an example of how to create your first project using the Bonita BPM Engine APIs and Maven.

Build and deploy the application
Reusable methods for REST
executePostRequest
executeGetRequest
executePutRequest
executeDeleteRequest
consumeResponse
ensureStatusOk
Build a process definition
Initialize application and create minimum configuration
Login and get userId
Deploy the process
Create minimum configuration
Display the menu and get the user's selection
Application options
Start a process case
List open process instances
List archived process instances
List pending tasks
Execute a task
Log out and shutdown

The application has the following sequence of actions:

  1. Initialize and start
  2. Build a process definition
  3. Create minimum configuration
  4. Log in a user
  5. Deploy the process
  6. Display menu of options to user
  7. Redisplay list and execute actions until user chooses to exit
  8. Shut down platform and exit application

All these actions use the Web REST API. To make this simpler, the application also contains a set of reusable methods for executing REST requests and for handling the responses:

  • executePostRequest
  • executeGetRequest
  • executePutRequest
  • executeDeleteRequest
  • consumeResponse
  • comsumeResponseIfNecessary
  • ensureStatusOk

Build and deploy the application

In this page, we assume you are using Tomcat and the h2 database in a development environment. This simplifies configuration of your platform.

To run the application, complete these steps:

  1. Download the source of the example from GitHub.
  2. Compile it with Maven: mvn clean install. This creates a jar file called rest-api-example-x.y.z-SNAPSHOT.jar and a zip file called rest-api-example-x.y.z-SNAPSHOT.zip where x.y.z is the version number of the application.
  3. Start Tomcat.
  4. Unzip rest-api-example-x.y.z-SNAPSHOT.zip.
  5. Move inside the unzipped folder.
  6. Start the application: rest-api-example-x.y.z-SNAPSHOT.jar.

When the application has started and completed the initialization steps, it displays a menu of choices. The application continues to run until you choose the Exit option.

Reusable methods for REST

The application uses several reusable methods to construct the URL and payload for REST requests. These methods are called with parameters that contain the specific information to be used in the URL and payload. There are also reusable methods for handling the responses.

executePostRequest

There are two versions of this method, one that takes a string containing the payload, and one that takes a URL-encoded entity.

// URL-encoded entity as input

   private int executePostRequest(String apiURI, UrlEncodedFormEntity entity) {
      try {
         HttpPost postRequest = new HttpPost(bonitaURI + apiURI);

         postRequest.setEntity(entity);

         HttpResponse response = httpClient.execute(postRequest, httpContext);

         return consumeResponse(response, true);

      } catch (HttpHostConnectException e) {
         throw new RuntimeException("Bonita bundle may not have been started, or the URL is invalid. Please verify hostname and port number. URL used is: " + BONITA_URI,e);
      } catch (Exception e) {
         e.printStackTrace();
         throw new RuntimeException(e);
      }

   }

// string as input

private HttpResponse executePostRequest(String apiURI, String payloadAsString) {
      try {
         HttpPost postRequest = new HttpPost(bonitaURI + apiURI);

         StringEntity input = new StringEntity(payloadAsString);
         input.setContentType("application/json");

         postRequest.setEntity(input);

         HttpResponse response = httpClient.execute(postRequest, httpContext);

         return response;

      } catch (Exception e) {
         e.printStackTrace();
         throw new RuntimeException(e);
      }
   }

executeGetRequest

private HttpResponse executeGetRequest(String apiURI) {
   try {
      HttpGet getRequest = new HttpGet(bonitaURI + apiURI);

      HttpResponse response = httpClient.execute(getRequest, httpContext);

      return response;

   } catch (Exception e) {
      e.printStackTrace();
      throw new RuntimeException(e);
   }

}
       

executePutRequest

private HttpResponse executePutRequest(String apiURI, String payloadAsString) {
   try {
       HttpPut putRequest = new HttpPut(bonitaURI + apiURI);
       putRequest.addHeader("Content-Type", "application/json");

       StringEntity input = new StringEntity(payloadAsString);
       input.setContentType("application/json");
       putRequest.setEntity(input);

       return httpClient.execute(putRequest, httpContext);

   } catch (Exception e) {
       e.printStackTrace();
       throw new RuntimeException(e);
   }
}

executeDeleteRequest

private HttpResponse executeDeleteRequest(String deleteURI) {
   try {

       HttpDelete deleteRequest = new HttpDelete(bonitaURI + deleteURI);
       HttpResponse response = httpClient.execute(deleteRequest, httpContext);

       return response;
   } catch (Exception e) {
       e.printStackTrace();
       throw new RuntimeException(e);
   }

}

consumeResponse

private int consumeResponse(HttpResponse response, boolean printResponse) {

    String responseAsString = consumeResponseIfNecessary(response);
    if(printResponse) {
       System.out.println(responseAsString);
    }

    return ensureStatusOk(response);
}

private String consumeResponseIfNecessary(HttpResponse response) {
        if (response.getEntity() != null) {
                BufferedReader rd;
                try {
                        rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));

                        StringBuffer result = new StringBuffer();
                        String line = "";
                        while ((line = rd.readLine()) != null) {
                                result.append(line);
                        }
                        return result.toString();
                } catch (Exception e) {
                        throw new RuntimeException("Failed to consume response.", e);
                }
        } else {
                return "";
        }
}

ensureStatusOk

private int ensureStatusOk(HttpResponse response) {
        if (response.getStatusLine().getStatusCode() != 201 && response.getStatusLine().getStatusCode() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode() + " : "
                                + response.getStatusLine().getReasonPhrase());
        }
        return response.getStatusLine().getStatusCode();
}

Build a process definition

The example builds a process definition using the processDefinitionBuilder. The definition is stored in a file called process.bar.

/**
 * Build a simple process: Start -> My Automatic Step -> My first step -> My second step -> End
 * Defines an actor mapping that will fit the organization deployed
 *
 * @return the built process
 * @throws InvalidProcessDefinitionException
 */

private File buildProcessDefinition() {
        try {
                String startName = "Start";
                String firstUserStepName = "My first step";
                String secondUserStepName = "My second step";
                String autoStepName = "My Automatic Step";
                String endName = "End";

                // create a new process definition with name and version
                ProcessDefinitionBuilder pdb = new ProcessDefinitionBuilder().createNewInstance(PROCESS_NAME, "1.0");
                // add actor defined as initiator
                pdb.addActor(ACTOR_NAME, true);
                // add a start event
                pdb.addStartEvent(startName);
                // add an automatic task
                pdb.addAutomaticTask(autoStepName);
                // add a user task having the previously defined actor
                pdb.addUserTask(firstUserStepName, ACTOR_NAME);
                // add another user task assigned to the previously defined actor
                pdb.addUserTask(secondUserStepName, ACTOR_NAME);
                // add an end event
                pdb.addEndEvent(endName);
                // defined transitions
                pdb.addTransition(startName, autoStepName);
                pdb.addTransition(autoStepName, firstUserStepName);
                pdb.addTransition(firstUserStepName, secondUserStepName);
                pdb.addTransition(secondUserStepName, endName);

        // embed actor mapping directly in definition
                BusinessArchive bar = new BusinessArchiveBuilder().createNewBusinessArchive().setProcessDefinition(pdb.done()).setActorMapping(getActorMapping()).done();
                File barOnFileSystem = new File(System.getProperty("java.io.tmpdir"), "process.bar");
                barOnFileSystem.deleteOnExit();
                BusinessArchiveFactory.writeBusinessArchiveToFile(bar, barOnFileSystem);

                return barOnFileSystem;
        } catch (Exception e) {
                throw new RuntimeException(e);
        }
}

When the definition is complete, the bar file is uploaded to the portal:

private String uploadGeneratedBar() throws IOException, ClientProtocolException {
        // build the process example
        File barFile = buildProcessDefinition();

        HttpPost post = new HttpPost(bonitaURI + "/portal/processUpload");

        MultipartEntity entity = new MultipartEntity();
        entity.addPart("file", new FileBody(barFile));
        post.setEntity(entity);

        HttpResponse response = httpClient.execute(post, httpContext);
        barFile.delete();
        return extractUploadedFilePathFromResponse(response);
}

Initialize application and create minimum configuration

In this example, several static variables are defined in the source code for simplicity. In a real application, these would be defined elsewhere, in configuration files or in the Engine database.

Variable Description Value
BONITA_URI The location and port for Bonita. This is used as the root term for all calls to the Web REST API. http://localhost:8080/bonita
TECHNICAL_USER_NAME The username of the tenant technical user. This is the only user that can log in before the organization is loaded. install
TECHNICAL_PASSWORD The password of the tenant technical user. install
USERNAME The username of the user who is logged in and uses the application. The user must be in the organization. walter.bates
PASSWORD The password of the user. bpm
ACTOR_NAME The actor assigned to human tasks in the process. MyActor
ORGANIZATION_FILE The XML file defining the organization. /ACME.xml
ACTOR_MAPPING_FILE The XML file defining the mapping between the process actor and the organization. /actorMapping.xml
PROCESS_NAME The name of the process that is defined in the application. My first process

After defining these variables, the example starts an HTTP client, using the methods provided by Tomcat, and then starts the application.

PoolingClientConnectionManager conMan = getConnectionManager();

App app = new App(new DefaultHttpClient(conMan), BONITA_URI);
app.start();

When the application starts, it logs in to Bonita as the technical user and loads the configuration required. First it checks that the locale is active. If the locale is not active, the API does not work after Tomcat restarts.

private void makeSureLocaleIsActive() {
        consumeResponse(executeGetRequest("/API/system/i18ntranslation?f=locale%3den"), false);

This method uses the generic executeGetRequest method to send a GET request for the locale, to make sure it can be read. The string in the method call is appended to the BONITA_URI value, so the full URL in the request is http://localhost:8080/bonita/API/system/i18ntranslation?f=locale%3den. The response is checked using the generic consumeResponse method.

If the locale is active, the organization is loaded from ORGANIZATION_FILE.

private void importOrganization() {
        importOrganizationFromFile(new File(getClass().getResource(ORGANIZATION_FILE).getPath()));
}

public int importOrganizationFromFile(File organizationFile) {

        try {
                System.out.println("Deploying organization ... ");
                HttpPost post = new HttpPost(bonitaURI + "/portal/organizationUpload");

                MultipartEntity entity = new MultipartEntity();
                entity.addPart("file", new FileBody(organizationFile));
                post.setEntity(entity);

                HttpResponse response = httpClient.execute(post, httpContext);
                String uploadedFilePath = extractUploadedFilePathFromResponse(response);

                String payloadAsString = "{\"organizationDataUpload\":\"" + uploadedFilePath + "\"}";
                int result = consumeResponse(executePostRequest("/services/organization/import", payloadAsString),false);

                System.out.println("Organization deployed!");
                return result;

        } catch (Exception e) {
                throw new RuntimeException(e);
        }

}
       
private String extractUploadedFilePathFromResponse(HttpResponse response) {
        try {
                return EntityUtils.toString(response.getEntity());
        } catch (Exception e) {
                throw new RuntimeException(e);
        }

}

During the initialization phase, the application displayed messages:

Messages displayed during initialization

Then the tenant technical user logs out. This is the end of the set-up phase of operation, and the main part of the App starts. There are three stages to this:

  1. Log in as the user defined by USERNAME (Walter Bates by default) and get the userId, which is needed to execute options.
  2. A loop: Display a list of options, execute the option selected by the user, redisplay the list of options, etc.
  3. When the user chooses the Exit option, log out of the Engine and close the connection.
long processId = 0;
try {
        // Behave as a user
        loginAs(USERNAME, PASSWORD);
        String userId = getUserIdFromSession();
        processId = deployProcess();

        // execute actions chosen from a menu
        executeActions(userId, processId);
} finally {
        // --- clean all information ----
        if (processId > 0) {
                // undeploy the process
                undeployProcess(processId);
        }

        logout();
        httpClient.getConnectionManager().shutdown();

Login and get userId

This method is called to log in as a user or as the tenant technical user.

       
public void loginAs(String username, String password) {

        try {

                CookieStore cookieStore = new BasicCookieStore();
                httpContext = new BasicHttpContext();
                httpContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);

                String loginURL = "/loginservice";

                // If you misspell a parameter you will get a HTTP 500 error
                List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
                urlParameters.add(new BasicNameValuePair("username", username));
                urlParameters.add(new BasicNameValuePair("password", password));
                urlParameters.add(new BasicNameValuePair("redirect", "false"));

                // UTF-8 is mandatory otherwise you get a NPE
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(urlParameters, "utf-8");
                executePostRequest(loginURL, entity);

        } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
        }

}

When the login in complete, the userId of the logged in user is obtained. This is done using a GET request containing an unused id. The response contains the userId of the logged in user, which is extracted from the substring. The userId is needed for some subsequent operations.

       
private String getUserIdFromSession() {
        try {
                HttpGet getRequest = new HttpGet(bonitaURI + "/API/system/session/unusedid");

                HttpResponse response = httpClient.execute(getRequest, httpContext);

                return extractUserIdFrom(response);

        } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
        }
}

private String extractUserIdFrom(HttpResponse response) {
        try {
                String session = EntityUtils.toString(response.getEntity());
                String remain = session.substring(session.indexOf("user_id\":") + 10);
                String userid = remain.substring(0, remain.indexOf("\""));
                return userid;
        } catch (Exception e) {
                throw new RuntimeException(e);
        }

}
       

Deploy the process

The process is deployed by uploading the bar file that contains the definition created in buildProcessDefinition, installing the process, and then enabling it.

The process is uploaded and installed using a POST request. The payload of the response to the POST contains the processId of the newly installed process. The process is then enabled using a PUT request. The request payload contains the extracted processId.

private long deployProcess() {
        try {
                System.out.println("Deploying process '" + PROCESS_NAME + "'...");
                String uploadedFilePath = uploadGeneratedBar();
                long processId = installProcessFromUploadedBar(uploadedFilePath);
                System.out.println("Process deployed with id: " + processId);

                System.out.println("Enabling process '" + PROCESS_NAME + "' (ID:" + processId + ")...");
                enableProcess(processId);
                System.out.println("Process Enabled!");
                return processId;
        } catch (Exception e) {
                throw new RuntimeException(e);
        }

}
       
private long installProcessFromUploadedBar(String uploadedFilePath) {
        String payloadAsString = "{\"fileupload\":\"" + uploadedFilePath + "\"}";

        return extractProcessId(executePostRequest("/API/bpm/process", payloadAsString));

}

private long extractProcessId(HttpResponse response) {
        ensureStatusOk(response);
        try {
                String processInJSON = EntityUtils.toString(response.getEntity());

                String remain = processInJSON.substring(processInJSON.indexOf("id\":") + 5);
                String id = remain.substring(0, remain.indexOf("\""));

                return Long.parseLong(id);
        } catch (Exception e) {
                throw new RuntimeException(e);
        }
}

       
private void enableProcess(long processId) {
        String payloadAsString = "{\"activationState\":\"ENABLED\"}";
        consumeResponse(executePutRequest("/API/bpm/process/" + processId, payloadAsString),true);
}

The application displays a message showing that the process is deployed:

Messages displayed during process deployment

Create minimum configuration

Before the process can run, an actor mapping file must be loaded. This provides the link between the actor defined in the process and the user who will run the process. The user must be defined in ORGANIZATION_FILE.

private byte[] getActorMapping() {

        FileInputStream fileInputStream = null;

        File file = new File(getClass().getResource(ACTOR_MAPPING_FILE).getPath());

        byte[] bFile = new byte[(int) file.length()];

        try {
                // convert file into array of bytes
                fileInputStream = new FileInputStream(file);
                fileInputStream.read(bFile);
        } catch (Exception e) {
                throw new RuntimeException(e);
        } finally {
                if (fileInputStream != null) {
                        try {
                                fileInputStream.close();
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
                }
        }

        return bFile;

}

Display the menu and get the user's selection

When the initialization is complete the application displays a menu of options to the user.

/**
 * Display a menu and prompt for a action to perform. The chosen action is
 * performed and a new action is prompted until the user decides to quit the
 * application.
 */

private void executeActions(String userId, long processID) {
        String message = getMenutTextContent();
        String choice = null;
        do {
                // show the menu and read the action chosen by the user
                choice = readLine(message);
                if ("1".equals(choice)) {
                        // if user chooses 1 then start a new process instance
                        startACase(processID);
                } else if ("2".equals(choice)) {
                        // if user chooses 2 then list opened process instances
                        listOpenedProcessInstances();
                } else if ("3".equals(choice)) {
                        // if user chooses 3 then list archived process instances
                        listArchivedProcessInstances();
                } else if ("4".equals(choice)) {
                        // if user chooses 4 then list pending tasks
                        listPendingTasks(userId);
                } else if ("5".equals(choice)) {
                        // if user chooses 5 execute the task chosen by the user
                        executeATask(userId);
                } else if (!"0".equals(choice)) {
                        System.out.println("Invalid choice!");
                }
        } while (!"0".equals(choice));
}
       
       
/**
 * Get the content of menu to be displayed
 *
 * @return the content of menu to be displayed
 */

private static String getMenutTextContent() {
        StringBuilder stb = new StringBuilder("\nChoose the action to be executed:\n");
        stb.append("0 - exit\n");
        stb.append("1 - start a process\n");
        stb.append("2 - list open process instances\n");
        stb.append("3 - list archived process instances\n");
        stb.append("4 - list pending tasks \n");
        stb.append("5 - execute a task\n");
        stb.append("Choice:");
        String message = stb.toString();
        return message;
}
       
/**
 * Read a line from standard input
 *
 * @param message
 *            the message to be displayed
 * @return the line read from standard input
 * @throws IOException
 *             if an exception occurs when reading a line
 */

private static String readLine(String message) {
        System.out.println(message);
        BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
        try {
                return buffer.readLine();
        } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
        }
}

The menu looks like this:

Menu

To select an option, type the corresponding number and pressing Return. The option is carried out, and then the menu is displayed again. This continues until you select the Exit option (0).

Application options

The following sections describe the options that the user can select, and show the corresponding REST requests and responses.

Start a process case

If you choose to start a case (process instance), a POST request is sent. The request payload contains the processDefinitionId. For example, if the processdefinitionId is 6700134106033735216 (as in the example above), the following request and payload are sent:

Request URL: http://localhost:8080/bonita/API/bpm/case/
Payload: {"processDefinitionId": "6700134106033735216"}

The response contains the processInstanceId. For example:

Messages displayed when a process instance starts
public int startACase(long processDefinitionId) {
        System.out.println("Starting a new case of process " + PROCESS_NAME + " (ID: " + processDefinitionId + ").");
        String apiURI = "/API/bpm/case/";

        String payloadAsString = "{\"processDefinitionId\": " + processDefinitionId + "}";

        return consumeResponse(executePostRequest(apiURI, payloadAsString),true);

}

List open process instances

If you choose to list the open process instances, a GET request is sent. To get a list of the first 100 cases, send the following GET request: http://localhost:8080/bonita/API/bpm/case/?p=0&c=100.

The response payload contains a list of the open process instances. The following example contains information about three open cases, with ids 10, 11, and 12.

List of open cases
private void listOpenedProcessInstances() {
        consumeResponse(executeGetRequest("/API/bpm/case?p=0&c=100"),true);
}

List archived process instances

Listing the archived process instances works in the same way as listing the open process instances. The only difference is the resource specified in the REST GET request, which is archivedCase instead of case. For example, to get the first 100 archived cases, send the following GET request: http://localhost:8080/bonita/API/bpm/archivedCase?p=0&c=100.

private void listArchivedProcessInstances() {
        consumeResponse(executeGetRequest("/API/bpm/archivedCase?p=0&c=100"),true);
}

List pending tasks

If you choose to list the pending tasks, the list shows the first 100 task at maximum. A GET request is sent. For example if your userId is 19, as in the example above, the URL is as follows: http://localhost:8080/bonita/API/bpm/humanTask?p=0&c=100&f=state%3dready&f=user_id%3d19.

The list of pending tasks is returned in the response payload. Each pending task has an id. You need to specify this id if you want to execute a task. The following example shows a result containing three pending tasks, with ids 30, 33, and 36.

List of pending tasks
/**
 * List 100 first pending tasks for the logged user
 */

public void listPendingTasks(String userId) {

        consumeResponse(executeGetRequest("/API/bpm/humanTask?p=0&c=100&f=state%3dready&f=user_id%3d" + userId),true);

}

Execute a task

If you choose to execute a task, you are prompted for the task id. When you specify the id, a PUT request is sent to assign the task to you. The request payload includes the task id and your user id. To execute task 30 as user 19, you send the following Put request: http://localhost:8080/bonita/API/bpm/humanTask/30. The payload is {"assigned_id": "19"}. The response to this includes an activityId, which identifies the task that is assigned to you. After the task is assigned to you, another PUT request is sent, specifying the activityId, and the task is executed. If the activityId returned is 42, the Put request is the following: http://localhost:8080/bonita/API/bpm/activity/42, and the payload is {"state":"completed"}.

Executing a task
private void executeATask(String userId) {
        // get the task id to be executed
        Long taskId = readTaskId();
        // a task cannot be executed if it is not assigned to the user
        assignActivity(taskId, userId);
        // execute the task
        executeActivity(taskId);

}

/**
 * Prompt for the task id to be executed
 *
 * @return the task id to be executed
 * @throws IOException
 */

private long readTaskId() {
        String message = "Enter the id of task to be executed:";
        String strId = readLine(message);
        long taskId = -1;
        try {
                taskId = Long.parseLong(strId);
        } catch (Exception e) {
                System.out.println(strId + " is not a valid id. You can find all task ids using the menu 'list pending tasks'.");
        }
        return taskId;
}

private void assignActivity(Long taskId, String userId) {
        String payloadAsString = "{\"assigned_id\":\"" + userId + "\"}";
        consumeResponse(executePutRequest("/API/bpm/humanTask/" + taskId, payloadAsString),true);

}
       
public void executeActivity(long activityId) {
        String apiURI = "/API/bpm/activity/" + activityId;
        String payloadAsString = "{\"state\":\"completed\"}";

        consumeResponse(executePutRequest(apiURI, payloadAsString),true);
}

Log out and shutdown

When you select the Exit option, you are logged out of the Engine, the process is undeployed and deleted, and the connection is closed.

public void logout() {
        consumeResponse(executeGetRequest("/logoutservice"),false);
}
       
private void undeployProcess(long processId) {

        disableProcess(processId);
        deleteProcess(processId);
}

private void disableProcess(long processId) {
        System.out.println("Disabling process '" + PROCESS_NAME + "' (ID:" + processId + ")...");
               
        String payloadAsString = "{\"activationState\":\"DISABLED\"}";
        consumeResponse(executePutRequest("/API/bpm/process/" + processId, payloadAsString),true);
               
        System.out.println("Process Disabled!");
}
       
private void deleteProcess(long processId) {
        System.out.println("Deleting process '" + PROCESS_NAME + "' (ID:" + processId + ")...");

        consumeResponse(executeDeleteRequest("/API/bpm/process/" + processId),true);

        System.out.println("Process deleted!");
}
Get the PDF version
Last update on Nov, 19 2015