JBoss.orgCommunity Documentation

Chapter 17. Remote API

17.1. Remote Java API
17.1.1. The REST Remote Java RuntimeEngine Factory
17.1.2. The JMS Remote Java RuntimeEngine Factory
17.2. REST
17.2.1. Additional Information
17.2.2. Runtime calls
17.2.3. History calls
17.2.4. Task calls
17.2.5. Deployment calls
17.2.6. Execute calls
17.3. JMS
17.3.1. JMS Queue setup
17.3.2. Using the remote Java API versus creating your own JMS requests
17.3.3. User identification for JMS messages using task requests
17.3.4. Serialization issues
17.3.5. Example JMS usage

The workbench contains an execution server (for executing processes and tasks), which also allows you to invoke various process and task related operations through a remote API. As a result, you can setup your process engine "as a service" and integrate this into your applications easily by doing remote requests and/or sending the necessary triggers to the execution server whenever necessary (without the need to embed or manage this as part of your application).

Both a REST and JMS based service are available (which you can use directly), and a Java remote client allows you to invoke these operations using the existing KieSession and TaskService interfaces (you also use for local interaction), making remote integration as easy as if you were interacting with a local process engine.

The Remote Java API provides KieSession, TaskService and AuditLogService interfaces to the JMS and REST APIs.

The interface implementations provided by the Remote Java API take care of the underlying logic needed to communicate with the JMS or REST APIs. In other words, these implementations will allow you to interact with a remote workbench instance (i.e. KIE workbench or the jBPM Console) via known interfaces such as the KieSession or TaskService interface, without having to deal with the underlying transport and serialization details.

In order to interact via REST with the remote runtime, the RemoteRestRuntimeFactory can be used. Through this factory, you can get access to a KieSession and TaskService, which you can then interact with the same way as if they were a local services. The following example illustrates how the remote session can be used.

// Setup the factory class with the necessarry information to communicate with the REST services

RemoteRestRuntimeFactory restSessionFactory 
  = new RemoteRestRuntimeFactory(deploymentId, baseUrl, user, password);
// Create KieSession and TaskService instances and use them
RuntimeEngine engine = restSessionFactory.newRuntimeEngine();
KieSession ksession = engine.getKieSession();
ProcessInstance processInstance = ksession.startProcess("com.burns.reactor.maintenance.cycle");
long procId = processInstance.getId();
  
String taskUserId = user;
TaskService taskService = engine.getTaskService();
List<TaskSummary> tasks = taskService.getTasksAssignedAsPotentialOwner(user, "en-UK");
  
long taskId = -1;
for( TaskSummary task : tasks ) { 
    if( task.getProcessInstanceId() == procId ) {
        taskId = task.getId();
    }
}
if( taskId == -1 ) { 
    throw new IllegalStateException("Unable to find task for " + user + " in process instance " + procId );
}
  
taskService.start(taskId, taskUserId);

The RemoteRestRuntimeFactory has one constructor which takes the arguments described in table below.


The Remote JMS Java RuntimeEngine works precisely the same as the REST variant, except that it has different constructors.

The RemoteJmsRuntimeEngineFactory has multiple constructors: one set of constructors allows the user to specify the JMS ConnectionFactory instance and each JMS Queue instance, while the other set takes an InitialContext instance that is expected to contain references to the ConnectionFactory and Queue instances.

Configuration using an InitialContext instance

When configuring the RemoteJmsRuntimeEngineFactory with an InitialContext instance as a parameter, it's necessary to retrieve the (remote) InitialContext instance first from the remote server. The following code illustrates how to do this.

public void startProcessAndTaskViaJmsRemoteJavaAPI(String serverHostName, String deploymentId, String user, String password) {

  // Setup remote JMS runtime engine factory
  InitialContext remoteInitialContext 
    = getRemoteInitialContext(serverHostName, user, password);
  int maxWaitTimeMillisecs = 5 * 1000;
  RemoteJmsRuntimeEngineFactory remoteJmsFactory
    = new RemoteJmsRuntimeEngineFactory(deploymentId, remoteInitialContext, user, password, maxWaitTimeMillisecs);
  // Interface with JMS api
  RuntimeEngine engine = remoteJmsFactory.newRuntimeEngine();
  KieSession ksession = engine.getKieSession();
  ProcessInstance processInstance = ksession.startProcess("com.burns.reactor.maintenance.cycle");
  long procId = processInstance.getId();
  TaskService taskService = engine.getTaskService();
  List<Long> tasks = taskService.getTasksByProcessInstanceId(procId);
  taskService.start(tasks.get(0), user);
}
private static InitialContext getRemoteInitialContext(String jbossServerHostName, String user, String password) { 
  // Configure the (JBoss AS 7/EAP 6) InitialContextFactory
  Properties initialProps = new Properties();
  initialProps.setProperty(InitialContext.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
  initialProps.setProperty(InitialContext.PROVIDER_URL, "remote://"+ jbossServerHostName + ":4447");
  initialProps.setProperty(InitialContext.SECURITY_PRINCIPAL, user);
  initialProps.setProperty(InitialContext.SECURITY_CREDENTIALS, password);
  for (Object keyObj : initialProps.keySet()) {
    String key = (String) keyObj;
    System.setProperty(key, (String) initialProps.get(key));
  }
  
  // Create the remote InitialContext instance
  try {
    return new InitialContext(initialProps);
  } catch (NamingException e) {
    throw new RuntimeException("Unable to create " + InitialContext.class.getSimpleName(), e);
  }
}

The following table describes the parameters used when using an InitialContext to configure a RemoteJmsRuntimeEngineFactory instance:


The following constructors are also available for users who wish to use the default quality of service threshold value (maximum wait time) for JMS messages, or otherwise configured the JMS queues to accept all connections:


Configuration using ConnectionFactory and Queue instance parameters

Some users may have direct access to a ConnectionFactory and the Queue instances needed to interact with the JMS API. In this case, they can use the RemoteJmsRuntimeEngineFactory constructor that uses the following arguments:


REST API calls to the execution server allow you to manage processes and tasks and retrieve various dynamic information from the execution server. The majority of the calls are synchronous, which means that the call will only complete, including the possible return of a result, once the requested operation has succeeded. The exceptions to this are the deployment POST calls, which will return the status of the request while the actual operation requested will asynchronously execute.

When using Java code to interface with the REST API, the classes used in POST operations or otherwise returned by various operations can be found in the (org.kie.remote:)kie-services-client JAR.

Except for the Execute calls , all other REST calls described below can use either JAXB or JSON.

All REST calls, unless otherwise specified, will use JAXB serialization.

When using JSON, make sure to add the JSON media type ( "application/json" ) to the ACCEPT header of your REST call.

Some of the REST calls below return lists of information. The results of these operations can be paginated , which means that the lists can be split up and returned according to the parameters sent by the user.

For example, if the REST call parameters indicate that page 2 with page size 10 should be returned for the results, then results 10 to (and including) 19 will be returned.

The first page is always page 1 (as opposed to page "0").


If both a "long" pagination parameter and its synonym are used, then only the value from the "long" variant is used. For example, if the page is given with a value of 11 and the p parameter is given with a value of 37, then the value of the page parameter, 11 , will be used and the p parameter will be ignored.

For the following operations, pagination is always used. See above for the default values used.


This section lists REST calls that interface with

The deploymentId component of the REST calls below must conform to the following regex:

[POST] /runtime/ {deploymentId} /process/ {processDefId} /start
[GET] /runtime/ {deploymentId} /process/instance/ {procInstId}
[POST] /runtime/ {deploymentId} /process/instance/ {procInstId+} /abort
[POST] /runtime/ {deploymentId} /process/instance/ {procInstId} /signal
[GET] /runtime/ {deploymentId} /process/instance/ {procInstId} /variables
[POST] /runtime/ {deploymentId} /signal
[GET] /runtime/ {deploymentId} /workitem/ {workItemId}
[POST] /runtime/ {deploymentId} /workitem/ {workItemId} /complete
[POST] /runtime/ {deploymentId} /workitem/{workItemId: [0-9-]+}/abort
[POST] /runtime/ {deploymentId} /history/clear
[GET] /runtime/ {deploymentId} /history/instances
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId}
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId} /child
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId} /node
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId} /variable
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId} /node/ {nodeId}
[GET] /runtime/ {deploymentId} /history/instance/ {procInstId} /variable/ {varId}
[GET] /runtime/ {deploymentId} /history/process/ {processDefId}
[GET] /runtime/ {deploymentId} /history/variable/ {varId}
[GET] /runtime/ {deploymentId} /history/variable/ {varId} /value/ {value}
[GET] /runtime/ {deploymentId} /history/variable/{varId}/instances
[GET] /runtime/ {deploymentId} /history/variable/{varId}/value/{value}/instances

The following section describes the three different types of task calls:

All of the task operation calls described in this section use the user (id) used in the REST basic authorization as input for the user parameter in the specific call.

Some of the operations take an optional lanaguage query parameter. If this parameter is not given as a element of the URL itself, the default value of " en-UK " is used.

The taskId component of the REST calls below must conform to the following regex:

[POST] /task/ {taskId} /activate
[POST] /task/ {taskId} /claim
[POST] /task/ {taskId} /claimnextavailable
[POST] /task/ {taskId} /complete
[POST] /task/ {taskId} /delegate
[POST] /task/ {taskId} /exit
[POST] /task/ {taskId} /fail
[POST] /task/ {taskId} /forward
[POST] /task/ {taskId} /nominate
[POST] /task/ {taskId} /release
[POST] /task/ {taskId} /resume
[POST] /task/ {taskId} /skip
[POST] /task/ {taskId} /start
[POST] /task/ {taskId} /stop
[POST] /task/ {taskId} /suspend
[GET] /task/query

The /task/query operation..

Except for the union parameter, if any of the other parameters are passed multiple times, this operation will query tasks based on the union of all values specific parameter. This is always true, regardless of the value of the union parameter.

For example, if multiple taskOwner parameters are passed, this operation will return all tasks that have a task owner matching at least one of the passed values.

However, behaviour with regards to multiple (types of) parameters is governed by the union parameter: if the union parameter is passed as false , then the operation will query based on the intersection of the two sets of values.

For example, if both a taskOwner and taskId parameter are passed as well as a union parameter with a value of false , then the operation will query for tasks that have both the specified task owner and task id.

However, if the union parameter in the above example is true , then the operation will query for tasks that have either the specified task owner or the specified task id.

The calls described in this section allow users to manage deployments. Deployments are in fact KieModule JARs which can be deployed or undeployed, either via the UI or via the REST calls described below. Configuration options, such as the runtime strategy, should be specified when deploying the deployment: the configuration of a deployment can not be changed after it has already been deployed.

The deploymentId component of the REST calls below must conform to the following regex:

The above regular expression is explained below:

The general idea behind the deploymentId is that it describes it contains the following elements, separated from eachother by a : character:

In addition to the asynchronous nature of the POST deploy and undeploy calls described above, the following information is also important to know:

While there is a /runtime/{id}/execute and a task/execute method, both will take all types of commands. This is possible because execute takes a JaxbCommandsRequest object, which contains a list of (org.kie.api.command.)Command objects. The JaxbCommandsRequest has fields to store the proper deploymentId and processInstanceId information.

Of course, if you send a command that needs this information ( deploymentId , for example) and don't fill it in, this will fail.

Runtime commands
AbortWorkItemCommandGetProcessInstancesCommandGetGlobalCommand
CompleteWorkItemCommandSetProcessInstanceVariablesCommandGetIdCommand
GetWorkItemCommandSignalEventCommandSetGlobalCommand
AbortProcessInstanceCommandStartCorrelatedProcessCommandDeleteCommand
GetProcessIdsCommandStartProcessCommandFireAllRulesCommand
GetProcessInstanceByCorrelationKeyCommandGetVariableCommandInsertObjectCommand
GetProcessInstanceCommandGetFactCountCommandUpdateCommand
Task commands
ActivateTaskCommandFailTaskCommandGetTasksOwnedCommand
AddTaskCommandForwardTaskCommandNominateTaskCommand
CancelDeadlineCommandGetAttachmentCommandProcessSubTaskCommand
ClaimNextAvailableTaskCommandGetContentCommandReleaseTaskCommand
ClaimTaskCommandGetTaskAssignedAsBusinessAdminCommandResumeTaskCommand
CompleteTaskCommandGetTaskAssignedAsPotentialOwnerCommandSkipTaskCommand
CompositeCommandGetTaskByWorkItemIdCommandStartTaskCommand
DelegateTaskCommandGetTaskCommandStopTaskCommand
ExecuteTaskRulesCommandGetTasksByProcessInstanceIdCommandSuspendTaskCommand
ExitTaskCommandGetTasksByStatusByProcessInstanceIdCommand 
Task commands
ClearHistoryLogsCommandFindProcessInstanceCommandFindSubProcessInstancesCommand
FindActiveProcessInstancesCommandFindProcessInstancesCommandFindVariableInstancesByNameCommand
FindNodeInstancesCommandFindSubProcessInstancesCommandFindVariableInstancesCommand

This section describes the setup and use of the JMS API.

The following is a rather long example that shows how to use the JMS API. The numbers ("callouts") along the side of the example refer to notes below that explain particular parts of the example. It's supplied for those advanced users that do not wish to use the jBPM Remote Java API.

The jBPM Remote Java API, described here, will otherwise take care of all of the logic shown below.

import static org.kie.services.client.serialization.SerializationConstants.*;


import java.util.List;
import java.util.UUID;
import javax.jms.*;
import javax.naming.*;
import javax.xml.bind.JAXBException;
import org.drools.core.command.runtime.process.StartProcessCommand;
import org.jbpm.services.task.commands.GetTaskAssignedAsPotentialOwnerCommand;
import org.kie.api.command.Command;
(1)import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.task.model.TaskSummary;
import org.kie.services.client.serialization.jaxb.JaxbSerializationProvider;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandsRequest;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandsResponse;
import org.kie.services.client.serialization.jaxb.impl.JaxbExceptionResponse;
// ...
  String USER = "charlie";
  String PASSWORD = "ch0c0licious";
  String DEPLOYMENT_ID = "test-project";
  String PROCESS_ID_1 = "oompa-processing";
  
(5)  // Create command
(2)  Command<?> cmd = new StartProcessCommand(PROCESS_ID_1);
  int oompaProcessingResultIndex = 0;
(5)  JaxbCommandsRequest req = new JaxbCommandsRequest(DEPLOYMENT_ID, cmd);
  req.getCommands().add(new GetTaskAssignedAsPotentialOwnerCommand(USER, "en-UK"));
  int loompaMonitoringResultIndex = 1;
  // Setup queues
  
  InitialContext context;
  Queue sendQueue, responseQueue;
  try { 
      context = new InitialContext();
      sendQueue = (Queue) context.lookup("jms/queue/KIE.SESSION");
      responseQueue = (Queue) context.lookup("jms/queue/KIE.RESPONSE");
  } catch( NamingException ne ) { 
     throw new RuntimeException("Unable to lookup send or response queue", ne); 
  }
  Connection connection = null;
  Session session = null;
  JaxbCommandsResponse cmdResponse = null;
  String corrId = UUID.randomUUID().toString();
  String selector = "JMSCorrelationID = '" + corrId + "'";
  try {
      // Create JMS connection and session
      MessageProducer producer;
      MessageConsumer consumer;
      try {
          ConnectionFactory connectionFactory = (ConnectionFactory) context.lookup("jms/RemoteConnectionFactory");
          connection = connectionFactory.createConnection(USER, PASSWORD);
          session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
          producer = session.createProducer(sendQueue);
          consumer = session.createConsumer(responseQueue, selector);
          connection.start();
      } catch (JMSException jmse) {
          throw new RuntimeException("Unable to setup a JMS connection.", jmse);
      } catch (NamingException ne) {
          throw new RuntimeException("Unable to lookup JMS connection factory.", ne);
      }
      // Create msg
(3)      BytesMessage msg;
(3)      try {
(3)          msg = session.createBytesMessage();
(3)          msg.setJMSCorrelationID(corrId);
          msg.setIntProperty("serialization", JaxbSerializationProvider.JMS_SERIALIZATION_TYPE);
          msg.setStringProperty(DEPLOYMENT_ID_PROPERTY_NAME, config.getDeploymentId());
          String xmlStr = JaxbSerializationProvider.convertJaxbObjectToString(req);
          msg.writeUTF(xmlStr);
      } catch (JMSException jmse) {
          throw new RuntimeException("Unable to create and fill a JMS message.", jmse);
      } catch (JAXBException jaxbe) {
          throw new RuntimeException("Unable to deserialze JMS message.", jaxbe);
      }
      // Send msg
      try {
          producer.send(msg);
      } catch (JMSException jmse) {
          throw new RuntimeException("Unable to send a JMS message.", jmse);
      }
      // receive
      Message response;
      try {
          long qualityOfServiceThresholdMilliSeconds = 5 * 1000;
          response = consumer.receive(qualityOfServiceThresholdMilliSeconds);
      } catch (JMSException jmse) {
          throw new RuntimeException("Unable to receive or retrieve the JMS response.", jmse);
      }
      // extract response
(4)      assert response != null : "Response is empty.";
      try {
          String xmlStr = ((BytesMessage) response).readUTF();
          cmdResponse = (JaxbCommandsResponse) JaxbSerializationProvider.convertStringToJaxbObject(xmlStr);
      } catch (JMSException jmse) {
          throw new RuntimeException("Unable to extract " + JaxbCommandsResponse.class.getSimpleName()
                  + " instance from JMS response.", jmse);
      } catch (JAXBException jaxbe) {
          throw new RuntimeException("Unable to extract " + JaxbCommandsResponse.class.getSimpleName()
                  + " instance from JMS response.", jaxbe);
      }
      assert cmdResponse != null : "Jaxb Cmd Response was null!";
  } finally {
      if (connection != null) {
          try {
              connection.close();
              session.close();
          } catch (JMSException jmse) {
              System.out.println("Unable to close connection or session!");
              jmse.printStackTrace();
          }
      }
  }
(6)  ProcessInstance oompaProcInst = null;
  List<TaskSummary> charliesTasks = null;
  for (JaxbCommandResponse<?> response : cmdResponse.getResponses()) {
      if (response instanceof JaxbExceptionResponse) {
(5)          JaxbExceptionResponse exceptionResponse = (JaxbExceptionResponse) response;
(6)          throw new RuntimeException(exceptionResponse.getMessage());
(5)      }
(6)      if (response.getIndex() == oompaProcessingResultIndex) {
          oompaProcInst = (ProcessInstance) response.getResult();
      } else if (response.getIndex() == loompaMonitoringResultIndex) {
          charliesTasks = (List<TaskSummary>) response.getResult();
      }
  }

1

These classes can all be found in the (org.kie.remote:)kie-services-client JAR.

2

The JaxbCommandsRequest instance is the "holder" object in which you can place all of the commands you want to execute in a particular request. By using the JaxbCommandsRequest.getCommands() method, you can retrieve the list of commands in order to add more commands to the request.

A deployment id is required for command request messages that deal with business processes. Command request messages that only contain human task-related commands do not require a deployment id.

3

Note that the JMS message sent to the remote JMS API must be constructed as follows:

  • It must be a JMS byte message.

  • It must have a filled JMS Correlation ID property.

  • It must have an int property with the name of "serialization" set to an acceptable value (only 0 at the moment).

  • It must contain a serialized instance of a JaxbCommandsRequest, added to the message as a UTF string

4

The same serialization mechanism used to serialize the request message will be used to serialize the response message.

5

In order to match the response to a command, to the initial command, use the index field of the returned JaxbCommandResponse instances. This index field will match the index of the initial command. Because not all commands will return a result, it's possible to send 3 commands with a command request message, and then receive a command response message that only includes one JaxbCommandResponse message with an index value of 1. That 1 then identifies it as the response to the second command.

6

Since many of the results returned by various commands are not serializable, the jBPM JMS Remote API converts these results into JAXB equivalents, all of which implement the JaxbCommandResponse interface. The JaxbCommandResponse.getResult() method then returns the JAXB equivalent to the actual result, which will conform to the interface of the result.

For example, in the code above, the StartProcessCommand returns a ProcessInstance. In order to return this object to the requester, the ProcessInstance is converted to a JaxbProcessInstanceResponse and then added as a JaxbCommandResponse to the command response message. The same applies to the List<TaskSummary> that's returned by the GetTaskAssignedAsPotentialOwnerCommand.

However, not all methods that can be called on a normal ProcessInstance can be called on the JaxbProcessInstanceResponse because the JaxbProcessInstanceResponse is simply a representation of a ProcessInstance object. This applies to various other command response as well. In particular, methods which require an active (backing) KieSession, such as ProcessInstance.getProess() or ProcessInstance.signalEvent(String type, Object event) will throw an UnsupportedOperationException.