Chapter 19. Intelligent Process Server Java Client API Overview

19.1. Client Configuration

You need to declare a configuration object and set server communication aspects, such as the protocol (REST or JMS), credentials and the payload format (XStream, JAXB or JSON). For additional example, follow the Hello World project.

Client Configuration

import org.kie.server.api.marshalling.MarshallingFormat;
import org.kie.server.client.KieServicesClient;
import org.kie.server.client.KieServicesConfiguration;
import org.kie.server.client.KieServicesFactory;

public class DecisionServerTest {

  private static final String URL = "http://localhost:8080/kie-server/services/rest/server";
  private static final String USER = "kieserver";
  private static final String PASSWORD = "kieserver1!";

  private static final MarshallingFormat FORMAT = MarshallingFormat.JSON;

  private KieServicesConfiguration conf;
  private KieServicesClient kieServicesClient;

  @Before
  public void initialize() {
    conf = KieServicesFactory.newRestConfiguration(URL, USER, PASSWORD);

    //If you use custom classes, such as Obj.class, add them to the configuration
    Set<Class<?>> extraClassList = new HashSet<Class<?>>();
    extraClassList.add(Obj.class);
    conf.addExtraClasses(extraClassList);

    conf.setMarshallingFormat(FORMAT);
    kieServicesClient = KieServicesFactory.newKieServicesClient(conf);
  }
}

JMS Client Configuration

import java.util.Properties;

import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.naming.Context;
import javax.naming.InitialContext;

import org.junit.Test;
import org.kie.server.client.KieServicesClient;
import org.kie.server.client.KieServicesConfiguration;
import org.kie.server.client.KieServicesFactory;

public class DecisionServerTest {

  private static final String REMOTING_URL = new String("remote://localhost:4447");

  private static final String USER = "kieserver";
  private static final String PASSWORD = "kieserver1!";

  private static final String INITIAL_CONTEXT_FACTORY = new String("org.jboss.naming.remote.client.InitialContextFactory");
  private static final String CONNECTION_FACTORY = new String("jms/RemoteConnectionFactory");
  private static final String REQUEST_QUEUE_JNDI = new String("jms/queue/KIE.SERVER.REQUEST");
  private static final String RESPONSE_QUEUE_JNDI = new String("jms/queue/KIE.SERVER.RESPONSE");

  private KieServicesConfiguration conf;
  private KieServicesClient kieServicesClient;

  @Test
  public void testJms() throws Exception {
    final Properties env = new Properties();
    env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
    env.put(Context.PROVIDER_URL, REMOTING_URL);
    env.put(Context.SECURITY_PRINCIPAL, USER);
    env.put(Context.SECURITY_CREDENTIALS, PASSWORD);
    InitialContext context = new InitialContext(env);

    Queue requestQueue = (Queue) context.lookup(REQUEST_QUEUE_JNDI);
    Queue responseQueue = (Queue) context.lookup(RESPONSE_QUEUE_JNDI);
    ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup(CONNECTION_FACTORY);

    conf = KieServicesFactory.newJMSConfiguration(connectionFactory, requestQueue, responseQueue, USER, PASSWORD);

    //If you use custom classes, such as Obj.class, add them to the configuration
    Set<Class<?>> extraClassList = new HashSet<Class<?>>();
    extraClassList.add(Obj.class);
    conf.addExtraClasses(extraClassList);

    kieServicesClient = KieServicesFactory.newKieServicesClient(conf);
  }
}

Note that you must assign the the guest role to the user kieserver. Additionally, you must declare JMS dependency:

<dependency>
  <groupId>org.jboss.as</groupId>
  <artifactId>jboss-as-jms-client-bom</artifactId>
  <version>7.5.7.Final-redhat-3</version>
  <type>pom</type>
</dependency>

19.1.1. JMS Interaction Patterns

Since version 6.4 of Red Hat JBoss BPM Suite, Intelligent Process Server Client integration with JMS has been enhanced by several interaction patterns. Available interaction patterns are:

  • Request reply: the default option that blocks the client until a response is received, making the JMS integration synchronous. Request reply is not suitable for a JMS transactional delivery.
  • Fire and forget: an option for one-way integration. Suitable, for example, for notifications invoked by integration with the Intelligent Process Server. Fire and forget is convenient for a transactional JMS delivery. Messages are delivered to the server only if the transaction that invoked the server client was committed successfully.
  • Asynchronous with callback: with this option, the client is not blocked after sending a message to Intelligent Process Server. Responses can be received asynchronously. This option can be used for the transactional JMS delivery.

You can set the response handlers either globally (when a KieServicesConfiguration is created) or individually on different client instances (such as RuleServiceClient, ProcessServicesClient, and others) during runtime.

Whereas fire and forget and request reply patterns do not require any additional configuration, you need to configure the callback if you use the asynchronous pattern. The Intelligent Process Server client includes a built-in callback (BlockingResponseCallback) that provides support using a blocking queue. The callback is configured to receive a single message at a time by default. Therefore, each client interaction contains a single message (request) and a single response. You can change the size of the queue to make it possible to receive multiple messages.

To create a custom callback, implement the org.kie.server.client.jms.ResponseCallback interface.

Note

Intelligent Process Server client is not thread-safe when switching response handlers. Change of a handler can affect all the threads which are using the same client instance. It is recommended to use separate client instances in case of dynamic changes of the handler. You can maintain a set of clients where each client uses a dedicated response handler. Depending on which handler is required, choose a respective client.

For example, having two clients, the first client (with the fire and forget pattern) can be used for starting processes and the second client (with the request reply pattern) can be used for querying user tasks.

Example 19.1. Global JMS Configuration

InitialContext context = ...;
Queue requestQueue = (Queue) context.lookup("jms/queue/KIE.SERVER.REQUEST");
Queue responseQueue = (Queue) context.lookup("jms/queue/KIE.SERVER.RESPONSE");
ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("jms/RemoteConnectionFactory");
KieServicesConfiguration jmsConfiguration = KieServicesFactory.newJMSConfiguration(connectionFactory, requestQueue, responseQueue, "user", "password");
// Set your response handler globally here.
jmsConfiguration.setResponseHandler(new FireAndForgetResponseHandler());

Example 19.2. Per Client JMS Configuration

ProcessServiceClient processClient = client.getServicesClient(ProcessServicesClient.class);
// Change response handler for processClient. The other clients are not affected.
processClient.setResponseHandler(new FireAndForgetResponseHandler());

In case you are using asynchronous or fire and forget response handlers, you can turn on JMS transactions in KieServicesConfiguration. If you do so, use a transaction-aware connection factory: XAConnectionFactory.

Warning

JMS transactions are supported only on Red Hat JBoss Enterprise Application Platform. JMS transactions are not tested on Oracle WebLogic Server and IBM WebSphere Application Server.

19.2. Server Response

Service responses are represented by the org.kie.server.api.model.ServiceResponse<T> object, where T represents the payload type. It has the following attributes:

  • String message: returns the response message.
  • ResponseType type: returns either SUCCESS or FAILURE.
  • T result: returns the requested object.

Example 19.3. Hello World Server Response

import org.kie.server.api.model.ServiceResponse;
import org.kie.server.client.RuleServicesClient;

RuleServicesClient ruleClient = client.getServicesClient(RuleServicesClient.class);
ServiceResponse<ExecutionResults> response = ruleClient.executeCommandsWithResults(container, batchCommand);
// Retrieve result with identifier output-object
Object result = response.getResult().getValue("output-object");
Note

A service response is retrieved only if you are using the request reply response handler. In case of asynchronous or fire and forget response handlers, all remote calls always return null.

19.3. Inserting and Executing Commands

To insert commands, use the org.kie.api.command.KieCommands class. To instantiate the KieCommands class, use org.kie.api.KieServices.get().getCommands(). If you want to add multiple commands, use the BatchExecutionCommand wrapper.

Example 19.4. Inserting and Executing Commands

import org.kie.api.command.Command;
import org.kie.api.command.KieCommands;
import org.kie.server.api.model.ServiceResponse;
import org.kie.server.client.RuleServicesClient;
import org.kie.server.client.KieServicesClient;
import org.kie.api.KieServices;

import java.util.Arrays;

...

public void executeCommands() {

  String containerId = "hello";
  System.out.println("== Sending commands to the server ==");
  RuleServicesClient rulesClient =
  kieServicesClient.getServicesClient(RuleServicesClient.class);
  KieCommands commandsFactory = KieServices.Factory.get().getCommands();

  Command<?> insert = commandsFactory.newInsert("Some String OBJ");
  Command<?> fireAllRules = commandsFactory.newFireAllRules();
  Command<?> batchCommand =
  commandsFactory.newBatchExecution(Arrays.asList(insert, fireAllRules));

  ServiceResponse<ExecutionResults> executeResponse =
  rulesClient.executeCommandsWithResults(containerId, batchCommand);

  if(executeResponse.getType() == ResponseType.SUCCESS) {
    System.out.println("Commands executed with success!");
    // Retrieve result with identifier output-object
    Object result = executeResponse.getResult().getValue("output-object");
  } else {
    System.out.println("Error executing rules. Message: ");
    System.out.println(executeResponse.getMsg());
  }
}

Add the org.drools:drools-compiler dependency into your pom.xml file. See the Supported Components Versions section of Red Hat JBoss BPM Suite Installation Guide to add a correct version.

<dependency>
  <groupId>org.drools-redhat</groupId>
  <artifactId>drools-compiler</artifactId>
  <version>6.5.0.Final-redhat-2</version>
</dependency>

See Embedded jBPM Engine Dependencies for a list of further Maven dependencies.

19.4. Listing Server Capabilities

From version 6.2, Intelligent Process Server supports the business process execution. To find out server capabilities, use the org.kie.server.api.model.KieServerInfo object.

KieServicesClient requires the server capability information to correctly produce service clients (see Section 19.7, “Available Intelligent Process Server Clients”). You can specify the capabilities globally in KieServicesConfiguration, otherwise they are automatically retrieved from the server.

Important

Regardless of which response handler is globally specified, KieServicesClient uses synchronous request response handler to retrieve the server capabilities. However, you cannot make synchronous calls when JMS transactions are enabled. To do so, you need to set the server capabilities in KieServicesConfiguration.

Example 19.5. Server Capabilities

public void listCapabilities() {

  KieServerInfo serverInfo = kieServicesClient.getServerInfo().getResult();
  System.out.print("Server capabilities:");

  for (String capability : serverInfo.getCapabilities()) {
    System.out.print(" " + capability);
  }

  System.out.println();
}

19.5. Listing Containers

Containers are represented by the org.kie.server.api.model.KieContainerResource object. The list of resources is represented by the org.kie.server.api.model.KieContainerResourceList object.

Example 19.6. Print a List of Containers

public void listContainers() {
    KieContainerResourceList containersList = kieServicesClient.listContainers().getResult();
    List<KieContainerResource> kieContainers = containersList.getContainers();
    System.out.println("Available containers: ");
    for (KieContainerResource container : kieContainers) {
        System.out.println("\t" + container.getContainerId() + " (" + container.getReleaseId() + ")");
    }
}

When obtaining the list of containers, you can optionally filter the result using an instance of the org.kie.server.api.model.KieContainerResourceFilter class, which is passed to the org.kie.server.client.KieServicesClient.listContainers() method.

Example 19.7. Filter Containers in Java Client API

public void listContainersWithFilter() {

    // The following filter will match only containers with the ReleaseId
    // "org.example:container:1.0.0.Final" and status FAILED
    KieContainerResourceFilter filter = new KieContainerResourceFilter.Builder()
            .releaseId("org.example", "container", "1.0.0.Final")
            .status(KieContainerStatus.FAILED)
            .build();

    // using previously created KieServicesClient....
    KieContainerResourceList containersList = kieServicesClient.listContainers(filter).getResult();
    List<KieContainerResource> kieContainers = containersList.getContainers();

    System.out.println("Available containers: ");

    for (KieContainerResource container : kieContainers) {
        System.out.println("\t" + container.getContainerId() + " (" + container.getReleaseId() + ")");
    }
}

19.6. Handling Containers

You can use the Intelligent Process Server Java client to create and dispose containers. If you dispose a container, ServiceResponse will be returned with void payload. If you create a container, KieContainerResource object will be returned.

Disposing and Creating a Container

public void disposeAndCreateContainer() {
    System.out.println("== Disposing and creating containers ==");
    List<KieContainerResource> kieContainers = kieServicesClient.listContainers().getResult().getContainers();
    if (kieContainers.size() == 0) {
        System.out.println("No containers available...");
        return;
    }
    KieContainerResource container = kieContainers.get(0);
    String containerId = container.getContainerId();
    ServiceResponse<Void> responseDispose = kieServicesClient.disposeContainer(containerId);
    if (responseDispose.getType() == ResponseType.FAILURE) {
        System.out.println("Error disposing " + containerId + ". Message: ");
        System.out.println(responseDispose.getMsg());
        return;
    }
    System.out.println("Success Disposing container " + containerId);
    System.out.println("Trying to recreate the container...");
    ServiceResponse<KieContainerResource> createResponse = kieServicesClient.createContainer(containerId, container);
    if(createResponse.getType() == ResponseType.FAILURE) {
        System.out.println("Error creating " + containerId + ". Message: ");
        System.out.println(responseDispose.getMsg());
        return;
    }
    System.out.println("Container recreated with success!");
}

Note

A conversation between a client and a specific Intelligent Process Server container in a clustered environment is secured by a unique conversationID. The conversationID is transferred using the X-KIE-ConversationId REST header. If you update the container, unset the previous conversationID. Use KieServiesClient.completeConversation() to unset the conversationID for Java API.

19.7. Available Intelligent Process Server Clients

KieServicesClient serves also as an entry point for other clients with the ability to perform various operations, such as Red Hat JBoss BRMS commands and manage processes. Following services are available in the org.kie.server.client package:

  • JobServicesClient is used to schedule, cancel, requeue, and get job requests.
  • ProcessServicesClient is used to start, signal, and abort processes or work items.
  • QueryServicesClient is used to query processes, process nodes, and process variables.
  • RuleServicesClient is used to send commands to the server to perform rule-related operations (for example insert objects into the working memory, fire rules, …​).

The org.kie.server.client.RuleServicesClient.executeCommands() API call was deprecated in version 6.3. The new org.kie.server.client.RuleServicesClient.executeCommandsWithResults() API returns execution results for objects that have been unmarshalled.

  • UserTaskServicesClient is used to perform all user-task operations (start, claim, cancel a task) and query tasks by specified field (process instances id, user, …​)
  • UIServicesClient is used to get String representation of forms (XML or JSON) and of the process image (SVG).
  • SolverServicesClient is used to perform all Business Resource Planner operations, such as getting the solver state and the best solution, or disposing of a solver.

The getServicesClient method provides access to any of these clients:

RuleServicesClient rulesClient = kieServicesClient.getServicesClient(RuleServicesClient.class);

19.8. Listing Available Business Processes

Use QueryClient to list available process definitions. QueryClient methods use pagination, therefore in addition to the query you make, you must provide the current page and the number of results per page. In the provided example, the query starts on page 0 and lists the first 1000 results.

List Processes

public void listProcesses() {
    System.out.println("== Listing Business Processes ==");
    QueryServicesClient queryClient = kieServicesClient.getServicesClient(QueryServicesClient.class);
    List<ProcessDefinition> findProcessesByContainerId = queryClient.findProcessesByContainerId("rewards", 0, 1000);
    for (ProcessDefinition def : findProcessesByContainerId) {
        System.out.println(def.getName() + " - " + def.getId() + " v" + def.getVersion());
    }
}

19.9. Starting a Business Processes

Use the ProcessServicesClient client to start your process. Ensure that any custom classes you require for your process are added into the KieServicesConfiguration object, using the addExtraClasses() method. To start a process using the Java Client API, see the following example:

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.kie.server.api.marshalling.MarshallingFormat;
import org.kie.server.client.KieServicesClient;
import org.kie.server.client.KieServicesConfiguration;
import org.kie.server.client.KieServicesFactory;
import org.kie.server.client.ProcessServicesClient;
...

public static void startProcess() {
  //Client configuration setup
  KieServicesConfiguration config = KieServicesFactory.newRestConfiguration(SERVER_URL, LOGIN, PASSWORD);

  //Add custom classes, such as Obj.class, to the configuration
  Set<Class<?>> extraClassList = new HashSet<Class<?>>();
  extraClassList.add(Obj.class);
  config.addExtraClasses(extraClassList);
  config.setMarshallingFormat(MarshallingFormat.JSON);

  // ProcessServicesClient setup
  KieServicesClient client = KieServicesFactory.newKieServicesClient(config);
  ProcessServicesClient processServicesClient = client.getServicesClient(ProcessServicesClient.class);

  // Create an instance of the custom class
  Obj obj = new Obj();
  obj.setOk("ok");

  Map<String, Object> variables = new HashMap<String, Object>();
  variables.put("test", obj);


  // Start the process with custom class
  processServicesClient.startProcess(CONTAINER, processId, variables);
}

19.10. QueryDefinition for Intelligent Process Server Using Java Client API

QueryDefinition is a feature used to execute advanced queries. For more information about advanced queries, see Section 16.7, “Advanced Queries for the Intelligent Process Server”. To register and execute query definitions using the Java Client API, see the following example:

Registering and Executing Query Definitions with QueryServicesClient

// client setup
KieServicesConfiguration conf = KieServicesFactory.newRestConfiguration(SERVER_URL, LOGIN, PASSWORD);
KieServicesClient client = KieServicesFactory.newKieServicesClient(conf);

// get the query services client
QueryServicesClient queryClient = client.getServicesClient(QueryServicesClient.class);

// building the query
QueryDefinition queryDefinition = QueryDefinition.builder().name(QUERY_NAME)
        .expression("select * from Task t")
        .source("java:jboss/datasources/ExampleDS")
        .target("TASK").build();

// two queries cannot have the same name
queryClient.unregisterQuery(QUERY_NAME);

// register the query
queryClient.registerQuery(queryDefinition);

// execute the query with parameters: query name, mapping type (to map the fields to an object), page number, page size and return type
List<TaskInstance> query = queryClient.query(QUERY_NAME, QueryServicesClient.QUERY_MAP_TASK, 0, 100, TaskInstance.class);

// read the result
for (TaskInstance taskInstance : query) {
    System.out.println(taskInstance);
}

Note that target instructs QueryService to apply default filters. Alternatively, you can set filter parameters manually. Target has the following values:

public enum Target {
    PROCESS,
    TASK,
    BA_TASK,
    PO_TASK,
    JOBS,
    CUSTOM;
}