Integratec API Platform
Getting Started

The following sections assume you've already successfully downloaded, installed, and configured the Integratec modules you're intending to use.

Starting Modules

There are two ways to start Integratec modules:

  1. Use exported opControlConnect functions via the Client API to communicate with installed Control services:
  2. Manually start each process by running the executable files.

Note: The Client API is not able to make requests until both the Master and a Worker module capable of processing the request are running.

Anatomy of a Request

At a high level, all service requests with the Integratec API consist of two elements:

  1. Service Identifier

    This allows the platform to uniquely identify a request and route it to the correct type of worker module.

  2. Request Parameters

    Arguments for a given request must be serialized as a JSON string, described in further detail below.

What is JSON?

See the following definition from json.org:

  • JSON (JavaScript Object Notation) is a lightweight data-interchange format. It is easy for humans to read and write, and is easy for machines to parse and generate.

Below is an example JSON object:

{"employees":
    [
        {"firstName":"John", "lastName":"Doe"},
        {"firstName":"Anna", "lastName":"Smith"},
        {"firstName":"Peter", "lastName":"Jones"}
    ]
}

All Integratec requests expect, at a bare minimum, an empty JSON object: {}. The great majority of them require specific parameters inside that object in order for the worker to know how to process the request.

How do I write JSON?

The most basic method is writing the string by hand:

C:

wchar_t *json = L"{\"this\":\"is a json string\"}";

C#:

string json = "{\"this\":\"is a json string\"}"

Java:

json = "{\"this\": \"is a json string\"}";

Python:

1 json = '{"this": "is a json string"}'

Depending on your language of choice, your mileage may vary with this method; at best it's useful for quick tests and samples. If you intend to really take programmatic advantage of the API, you'll want to use a library that provides an easy-to-use interface for serializing your data into JSON. The following is a list of suggested libraries for the languages we support:

Unique Names

The JSON specification RFC 4627 states that "The names within an object SHOULD be unique." For example, the following JSON is valid, but the names inside the object are not unique.

{
    "exampleName": "value1",
    "exampleName": "value2"
}

Integratec checks the request object for non-unique names. It also recursively checks object and array type properties for non-unique names. If it finds a non-unique name, it reports an error and does not send the request.

JSON-Schema

One of your greatest resources for understanding what to send and expect back from the Integratec platform is a series of schema files found in the /schemas folder within each local installation. Each service we offer has an associated schema file for both the Request and the Reply: <ServiceID>-Req.json and <ServiceID>-Rep.json, respectively.

IMPORTANT: DO NOT MODIFY THE SCHEMA FILES. IT CAN CAUSE SERIOUS UNEXPECTED ERRORS.

We use JSON-Schema to describe and validate the Request parameters (as well as the Reply result). You can read an excellent tutorial on JSON-Schema here. See below:

Example Schema:

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "properties": {
        "id": {
            "type": "integer"
        }
    },
    "required": ["id"]
}

Example Matching JSON:

{"id": 42}

Basic Pattern

Once all necessary modules are started, you can begin making requests of the Integratec platform.

Requests made by the Client API are sent to the Master, which routes them to an appropriate worker thread. Work is assigned with load-balancing (only available worker threads accept requests) and fair queuing (worker threads take turns accepting requests, even if all are available).

request-reply_pattern.bmp
Request-Reply Pattern

Making requests is accomplished via the following exported functions:

Open Connection

The first step is opening a Client API connection to the Integratec platform:

(c)

// Import the wrapper
#include "opApi.h"
// Include stddef.h to get declaration for NULL
#include <stddef.h>
// Open a connection
OPAPIHandle api;
wchar_t *brokerId = NULL;
wchar_t *errorMsg = NULL;
opClientConnectionOpen(L"127.0.0.1", 60000, NULL, &api, &brokerId, &errorMsg);

(c#)

// Import the wrapper
using opNetApi;
// Instantiate the object
opClientConnection api = new opClientConnection();
// Open a connection
api.open("127.0.0.1", 60000)
// Alternatively, you can instantiate and open in the same call
opClientConnection api = new opClientConnection("127.0.0.1", 60000);

(java)

// Import the wrapper
import opJavaApi.*;
// Instantiate the object
OPClientConnection api = new OPClientConnection();
// Open a connection
api.open("127.0.0.1", 60000);
// Alternatively, you can instantiate and open in the same call
OPClientConnection api = new OPClientConnection("127.0.0.1", 60000);

(py)

1 # Import the wrapper
2 from opPyApi import *
3 
4 # Instantiate the object
5 api = opClientConnection()
6 
7 # Open a connection
8 api.open('127.0.0.1', 60000)

Note: An opClientConnection object is NOT thread-safe, meaning that while you are allowed to use the object in multiple threads, you must ensure that you are not calling its methods simultaneously in multiple threads.

Send Request

Your version of "Hello, world." is using the service GetBrokerIdentity, which returns the internal identifier of the Master as a string. We use the sendRequest function, which initially returns a unique identifier (e.g. "{7BF91CB7-AAF6-435E-AEA3-6E67DE910A8C}"). This 'guid' is useful for getting the reply, filtering status updates, and cleaning up when you're done.

(c)

// Set input params
wchar_t *serviceId = L"getBrokerIdentity";
wchar_t *requestJson = L"{}";
// Generate a request
wchar_t *guid;
wchar_t *errorMsg;
opClientConnectionSendRequest(api, serviceId, requestJson, &guid, &errorMsg);

(c#)

// Set input params
string serviceId = 'getBrokerIdentity';
string requestJson = '{}';
// Generate a request
string guid;
guid = api.sendRequest(serviceId, requestJson);

(java)

// Set input params
String serviceId = "getBrokerIdentity";
String requestJson = "{}";
// Generate a request
String guid = api.sendRequest(serviceId, requestJson);

(py)

1 # Set input params
2 serviceId = 'getBrokerIdentity'
3 requestJson = '{}'
4 
5 # Generate a request
6 guid = api.sendRequest(serviceId, requestJson)

Note: There are no parameters for a GetBrokerIdentity service request, hence the empty requestJson JSON object.

Receive Reply

Now that you have the 'guid', you can ask for the reply. It is possible to generate a request with one client connection, and to get the reply with another, assuming you saved the unique identifier.

The reply from the thread doing the work is always a JSON object, and adheres to that service's reply schema, just as you were required to adhere to the service's request schema.

(c)

// Get the reply for the specified request ID with a timeout of 100ms
wchar_t *replyJson;
wchar_t *errorMsg;
opClientConnectionReceiveReply(api, guid, 100, &replyJson, &errorMsg);

(c#)

// Get the reply for the specified request ID with a timeout of 100ms
string replyJson;
replyJson = api.receiveReply(guid, 100);

(java)

// Get the reply for the specified request ID with a timeout of 100ms
String replyJson = api.receiveReply(guid, 100);

(py)

1 # Get the reply for the specified request ID with a timeout of 100ms
2 replyJson = api.receiveReply(guid, 100)

Note: The timeout parameter is set to 100 milliseconds. If the timeout is reached, an exception is raised.

The value of the replyJson variable should look something like this:

{
    "identity": "Master-DABB9"
}

Delete Request

When a request is generated, both the Master and client retain record of the request. Using the deleteRequest function deletes that record. Again, you supply the unique identifier for the request and all record of it is removed.

(c)

// Clean up record of the specified request
wchar_t *errorMsg;
opClientConnectionDeleteRequest(api, guid, &errorMsg);

(c#)

// Clean up record of the specified request
api.deleteRequest(guid);

(java)

// Clean up record of the specified request
api.deleteRequest(guid);

(py)

1 # Clean up record of the specified request
2 api.deleteRequest(guid)

Send, Receive, Delete

We've also provided a convenient method for sending a request, receiving the reply, and deleting record of it all synchronously, tied to the same timeout. The 'guid' for this request is not available, as record of the request is always deleted upon exiting the call.

(c)

// Create a request, get the reply, and clean up the request
opClientConnectionSendRecvDelete(api, serviceId, requestJson, 100, &replyJson, &errorMsg);

(c#)

// Create a request, get the reply, and clean up the request
replyJson = api.sendRecvDelete(serviceId, requestJson, 100);

(java)

// Create a request, get the reply, and clean up the request
replyJson = api.sendRecvDelete(serviceId, requestJson, 100);

(py)

1 # Create a request, get the reply, and clean up the request
2 replyJson = api.sendRecvDelete(serviceId, requestJson, 100)

Close Connection

The last step is closing the connection:

(c)

// Close the client connection

(c#)

// Close the client connection
api.close();

(java)

// Close the client connection
api.close();

(py)

1 # Close the client connection
2 api.close()

Request Status

You can track the status of a request by implementing a status callback function, or where supported, making use of an event handler (e.g. .NET). Worker threads publish status updates once at startup, multiple times to represent progress, and once again when work is completed. The following diagram illustrates the sequence of messages, omitting the always-present routing Master module:

request_sequence.bmp
Message Sequence

There are two parameters to the callback function, RequestID and Status:

RequestID

This is the unique identifier you received from generating the request.

Status

The status string is serialized as JSON, and always contains type and TaskRunID elements. See the following examples for the three possible types:

{ "statusType": "Started", "TaskRunID": 1, "MinionID": "Minion-AJ82JA4" }

  • type describes the status update, allowing you to parse the other elements.
  • TaskRunID defaults to 1, and corresponds to a record in the persist table.
  • MinionID indicates which worker thread was assigned the request.

{ "statusType": "Progress", "TaskRunID": 1, "currentPosition": 42, "maximumPosition": 9000, "message": "Doing all the things!" }

  • currentPosition and maximumPosition are the real-time progress of the request. A null value is considered indeterminate.
  • message is a caption-like string that describes the work currently in-progress

{ "statusType": "Finished", "TaskRunID": 1 }

Note: Status updates do not wait in a queue for you get them. If you're not around to receive them, they are lost forever. Because of this, it is unwise to rely on them. In contrast, the reply for each RequestID will be waiting for you via receiveReply regardless of which Client API connection you're on, or whether or not you had to reconnect, until you delete it with deleteRequest.

Using status updates requires implementing a callback function. For example:

(c)

// Include stdio.h to get declaration for wprintf()
#include <stdio.h>
// Implement an example callback to print the id and status given by status
void _statusCallback(OPAPIHandle sender, wchar_t *id, wchar_t *status)
{
wprintf(L"The status of id {%ws} is {w%s}.", id, status);
}
void main()
{
// Pass in the status callback pointer argument
OPAPIHandle api;
wchar_t *brokerId;
wchar_t *errorMsg;
opClientConnectionOpen(L"127.0.0.1", 60000, &_statusCallback, &api, &brokerId, &errorMsg);
}

(c#)

using opNetApi;
namespace opConsole
{
class Program
{
static void Main(string[] args)
{
opClientConnection op = new opClientConnection("127.0.0.1", 60000);
// Set the local method to the callback event
op.statusEvent += op_statusEvent;
}
// Implement an example callback to print the id and status given by status
static void op_statusEvent(object sender, opClientConnection.statusEventArgs e)
{
Console.WriteLine("The status of id {0} is {1}.", e.id, e.status);
}
}
}

(java)

import com.sun.jna.WString;
import opJavaApi.*;
// Implement an example callback to print the status id and message
class StatusCallback implements OPStatusCallback {
@Override
public void statusCallback(int connection, WString id, WString statusMsg) {
System.out.println(id);
System.out.println(statusMsg);
}
}
public class OPClient {
public static void main(String[] args) throws OPException, java.lang.InterruptedException {
// Instantiate the callback object
StatusCallback callback = new StatusCallback();
// Pass in the status callback pointer argument
OPClientConnection api = new OPClientConnection("127.0.0.1", 60000, callback);
}
}

(py)

1 # Implement an example callback to print the id and status given by status
2 def statusCallback(connection, id, status):
3  print id.encode('utf-8')
4  print status.encode('utf-8')
5 
6 # Pass in the status callback pointer argument
7 api = OPApi('../path/to/your/local/install')
8 api.open('127.0.0.1', 60000, statusCallback)